Załóżmy na przykład, że chcę ICar
interfejs i że wszystkie implementacje będą zawierać pole Year
. Czy to oznacza, że każde wdrożenie musi być osobno zadeklarowane Year
? Czy nie byłoby lepiej po prostu zdefiniować to w interfejsie?
Załóżmy na przykład, że chcę ICar
interfejs i że wszystkie implementacje będą zawierać pole Year
. Czy to oznacza, że każde wdrożenie musi być osobno zadeklarowane Year
? Czy nie byłoby lepiej po prostu zdefiniować to w interfejsie?
Odpowiedzi:
Chociaż wiele innych odpowiedzi jest poprawnych na poziomie semantycznym, ciekawe jest również podejście do tego rodzaju pytań z poziomu szczegółów implementacji.
Interfejs można traktować jako zbiór gniazd , które zawierają metody . Gdy klasa implementuje interfejs, klasa musi poinformować środowisko wykonawcze, jak wypełnić wszystkie wymagane szczeliny. Kiedy powiesz
interface IFoo { void M(); }
class Foo : IFoo { public void M() { ... } }
klasa mówi: „kiedy utworzysz instancję mnie, umieść odniesienie do Foo.M w gnieździe dla IFoo.M.
Następnie, gdy wykonujesz połączenie:
IFoo ifoo = new Foo();
ifoo.M();
kompilator generuje kod, który mówi „zapytaj obiekt, która metoda znajduje się w gnieździe dla IFoo.M, i wywołaj tę metodę.
Jeśli interfejs jest zbiorem gniazd, które zawierają metody, niektóre z tych gniazd mogą również zawierać metody get i set właściwości, metody get and set indeksatora oraz metody dodawania i usuwania zdarzenia. Ale pole nie jest metodą . Nie ma „pola” związanego z polem, które można następnie „wypełnić” z odniesieniem do położenia pola. Dlatego interfejsy mogą definiować metody, właściwości, indeksatory i zdarzenia, ale nie pola.
An interface cannot contain constants, fields, operators
. Z msdn.microsoft.com/en-us/library/ms173156.aspx )
int One()
, implementacja public int One(){return 1;}
nie jest polem.
Interfejsy w języku C # mają na celu zdefiniowanie kontraktu, do którego klasa będzie się stosować - a nie konkretnej implementacji.
W tym duchu interfejsy C # umożliwiają zdefiniowanie właściwości - które wywołujący musi dostarczyć implementację dla:
interface ICar
{
int Year { get; set; }
}
Klasy implementujące mogą korzystać z auto-właściwości w celu uproszczenia implementacji, jeśli nie ma specjalnej logiki związanej z tą właściwością:
class Automobile : ICar
{
public int Year { get; set; } // automatically implemented
}
Year
?
public int Year => 123;
. Jednak w tym przypadku nie ma sensu mieć settera, więc interfejs musiałby zostać zdefiniowany za pomocąint Year { get; }
Eric Lippert go przybił, użyję innego sposobu, aby powiedzieć, co powiedział. Wszyscy członkowie interfejsu są wirtualni i wszyscy muszą zostać zastąpieni przez klasę, która dziedziczy interfejs. Nie podajesz wprost wirtualnego słowa kluczowego w deklaracji interfejsu ani nie używasz słowa kluczowego zastępującego w klasie, są one implikowane.
Wirtualne słowo kluczowe jest implementowane w .NET z metodami i tak zwaną tabelą v, tablicą wskaźników metod. Słowo kluczowe override wypełnia pole tabeli v innym wskaźnikiem metody, zastępując ten wygenerowany przez klasę podstawową. Właściwości, zdarzenia i indeksatory są wdrażane jako metody pod maską. Ale pola nie są. Interfejsy nie mogą zatem zawierać pól.
Dlaczego nie mieć po prostu Year
nieruchomości, która jest całkowicie w porządku?
Interfejsy nie zawierają pól, ponieważ pola reprezentują określoną implementację reprezentacji danych, a ich ujawnienie spowodowałoby przerwanie enkapsulacji. Zatem posiadanie interfejsu z polem skutecznie koduje implementację zamiast interfejsu, co jest dziwnym paradoksem dla interfejsu!
Na przykład część Year
specyfikacji może wymagać, aby ICar
implementatorzy zezwolili na przypisanie do Year
późniejszej wersji niż 1 lub przed 1900 rokiem. Nie można powiedzieć, że jeśli odsłoniłeś Year
pola - zdecydowanie lepiej jest użyć właściwości, aby wykonać pracę tutaj.
Krótka odpowiedź brzmi: tak, każdy typ implementacji będzie musiał stworzyć własną zmienną wspierającą. Wynika to z faktu, że interfejs jest analogiczny do umowy. Wszystko, co może zrobić, to określić konkretne publicznie dostępne fragmenty kodu, które typ implementacyjny musi udostępnić; nie może zawierać żadnego kodu.
Rozważ ten scenariusz, używając tego, co sugerujesz:
public interface InterfaceOne
{
int myBackingVariable;
int MyProperty { get { return myBackingVariable; } }
}
public interface InterfaceTwo
{
int myBackingVariable;
int MyProperty { get { return myBackingVariable; } }
}
public class MyClass : InterfaceOne, InterfaceTwo { }
Mamy tutaj kilka problemów:
myBackingVariable
będzie MyClass
używać?Najczęstszym podejściem jest zadeklarowanie interfejsu i klasy abonenckiej, która go implementuje. Zapewnia to elastyczność dziedziczenia po klasie abstrakcyjnej i uzyskania implementacji za darmo lub jawnego wdrożenia interfejsu i pozwolenia na dziedziczenie z innej klasy. Działa to mniej więcej tak:
public interface IMyInterface
{
int MyProperty { get; set; }
}
public abstract class MyInterfaceBase : IMyInterface
{
int myProperty;
public int MyProperty
{
get { return myProperty; }
set { myProperty = value; }
}
}
Interfejsy nie zawierają żadnej implementacji.
Wiele już powiedziano, ale dla uproszczenia, oto moje zdanie. Interfejsy mają zawierać umowy metod do wdrożenia przez konsumentów lub klasy, a nie pola do przechowywania wartości.
Możesz argumentować, że w takim razie dlaczego właściwości są dozwolone? Tak więc prosta odpowiedź brzmi - właściwości są wewnętrznie zdefiniowane tylko jako metody.
W tym celu możesz mieć klasę bazową Samochód, która implementuje pole roku, a wszystkie inne implementacje mogą dziedziczyć po nim.
Interfejs definiuje właściwości i metody instancji publicznych . Pola są zwykle prywatne lub w najwyższym stopniu chronione, wewnętrzne lub chronione wewnętrzne (termin „pole” zwykle nie jest używany w przypadku jakichkolwiek publicznych).
Jak stwierdzono w innych odpowiedziach, możesz zdefiniować klasę podstawową i zdefiniować właściwość chronioną, która będzie dostępna dla wszystkich spadkobierców.
Jedną z osobliwości jest to, że interfejs można w rzeczywistości zdefiniować jako wewnętrzny, ale ogranicza on użyteczność interfejsu i zwykle jest używany do definiowania wewnętrznej funkcjonalności, która nie jest używana przez inny kod zewnętrzny.