An interface defines a contract. A class or struct that implements an interface must adhere to its contract. An interface may inherit from multiple base interfaces, and a class or struct may implement multiple interfaces.
Interfaces can contain methods, properties, events, and indexers. The interface itself does not provide implementations for the members that it defines. The interface merely specifies the members that must be supplied by classes or structs that implement the interface.
An interface_declaration is a type_declaration (Type declarations) that declares a new interface type.
interface_declaration
: attributes? interface_modifier* 'partial'? 'interface'
identifier variant_type_parameter_list? interface_base?
type_parameter_constraints_clause* interface_body ';'?
;
An interface_declaration consists of an optional set of attributes (Attributes), followed by an optional set of interface_modifiers (Interface modifiers), followed by an optional partial
modifier, followed by the keyword interface
and an identifier that names the interface, followed by an optional variant_type_parameter_list specification (Variant type parameter lists), followed by an optional interface_base specification (Base interfaces), followed by an optional type_parameter_constraints_clauses specification (Type parameter constraints), followed by an interface_body (Interface body), optionally followed by a semicolon.
An interface_declaration may optionally include a sequence of interface modifiers:
interface_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| interface_modifier_unsafe
;
It is a compile-time error for the same modifier to appear multiple times in an interface declaration.
The new
modifier is only permitted on interfaces defined within a class. It specifies that the interface hides an inherited member by the same name, as described in The new modifier.
The public
, protected
, internal
, and private
modifiers control the accessibility of the interface. Depending on the context in which the interface declaration occurs, only some of these modifiers may be permitted (Declared accessibility).
The partial
modifier indicates that this interface_declaration is a partial type declaration. Multiple partial interface declarations with the same name within an enclosing namespace or type declaration combine to form one interface declaration, following the rules specified in Partial types.
Variant type parameter lists can only occur on interface and delegate types. The difference from ordinary type_parameter_lists is the optional variance_annotation on each type parameter.
variant_type_parameter_list
: '<' variant_type_parameters '>'
;
variant_type_parameters
: attributes? variance_annotation? type_parameter
| variant_type_parameters ',' attributes? variance_annotation? type_parameter
;
variance_annotation
: 'in'
| 'out'
;
If the variance annotation is out
, the type parameter is said to be covariant. If the variance annotation is in
, the type parameter is said to be contravariant. If there is no variance annotation, the type parameter is said to be invariant.
In the example
interface C<out X, in Y, Z>
{
X M(Y y);
Z P { get; set; }
}
X
is covariant, Y
is contravariant and Z
is invariant.
The occurrence of variance annotations in the type parameter list of a type restricts the places where types can occur within the type declaration.
A type T
is output-unsafe if one of the following holds:
T
is a contravariant type parameterT
is an array type with an output-unsafe element typeT
is an interface or delegate typeS<A1,...,Ak>
constructed from a generic typeS<X1,...,Xk>
where for at least oneAi
one of the following holds:Xi
is covariant or invariant andAi
is output-unsafe.Xi
is contravariant or invariant andAi
is input-safe.
A type T
is input-unsafe if one of the following holds:
T
is a covariant type parameterT
is an array type with an input-unsafe element typeT
is an interface or delegate typeS<A1,...,Ak>
constructed from a generic typeS<X1,...,Xk>
where for at least oneAi
one of the following holds:Xi
is covariant or invariant andAi
is input-unsafe.Xi
is contravariant or invariant andAi
is output-unsafe.
Intuitively, an output-unsafe type is prohibited in an output position, and an input-unsafe type is prohibited in an input position.
A type is output-safe if it is not output-unsafe, and input-safe if it is not input-unsafe.
The purpose of variance annotations is to provide for more lenient (but still type safe) conversions to interface and delegate types. To this end the definitions of implicit (Implicit conversions) and explicit conversions (Explicit conversions) make use of the notion of variance-convertibility, which is defined as follows:
A type T<A1,...,An>
is variance-convertible to a type T<B1,...,Bn>
if T
is either an interface or a delegate type declared with the variant type parameters T<X1,...,Xn>
, and for each variant type parameter Xi
one of the following holds:
Xi
is covariant and an implicit reference or identity conversion exists fromAi
toBi
Xi
is contravariant and an implicit reference or identity conversion exists fromBi
toAi
Xi
is invariant and an identity conversion exists fromAi
toBi
An interface can inherit from zero or more interface types, which are called the explicit base interfaces of the interface. When an interface has one or more explicit base interfaces, then in the declaration of that interface, the interface identifier is followed by a colon and a comma separated list of base interface types.
interface_base
: ':' interface_type_list
;
For a constructed interface type, the explicit base interfaces are formed by taking the explicit base interface declarations on the generic type declaration, and substituting, for each type_parameter in the base interface declaration, the corresponding type_argument of the constructed type.
The explicit base interfaces of an interface must be at least as accessible as the interface itself (Accessibility constraints). For example, it is a compile-time error to specify a private
or internal
interface in the interface_base of a public
interface.
It is a compile-time error for an interface to directly or indirectly inherit from itself.
The base interfaces of an interface are the explicit base interfaces and their base interfaces. In other words, the set of base interfaces is the complete transitive closure of the explicit base interfaces, their explicit base interfaces, and so on. An interface inherits all members of its base interfaces. In the example
interface IControl
{
void Paint();
}
interface ITextBox: IControl
{
void SetText(string text);
}
interface IListBox: IControl
{
void SetItems(string[] items);
}
interface IComboBox: ITextBox, IListBox {}
the base interfaces of IComboBox
are IControl
, ITextBox
, and IListBox
.
In other words, the IComboBox
interface above inherits members SetText
and SetItems
as well as Paint
.
Every base interface of an interface must be output-safe (Variance safety). A class or struct that implements an interface also implicitly implements all of the interface's base interfaces.
The interface_body of an interface defines the members of the interface.
interface_body
: '{' interface_member_declaration* '}'
;
The members of an interface are the members inherited from the base interfaces and the members declared by the interface itself.
interface_member_declaration
: interface_method_declaration
| interface_property_declaration
| interface_event_declaration
| interface_indexer_declaration
;
An interface declaration may declare zero or more members. The members of an interface must be methods, properties, events, or indexers. An interface cannot contain constants, fields, operators, instance constructors, destructors, or types, nor can an interface contain static members of any kind.
All interface members implicitly have public access. It is a compile-time error for interface member declarations to include any modifiers. In particular, interfaces members cannot be declared with the modifiers abstract
, public
, protected
, internal
, private
, virtual
, override
, or static
.
The example
public delegate void StringListEvent(IStringList sender);
public interface IStringList
{
void Add(string s);
int Count { get; }
event StringListEvent Changed;
string this[int index] { get; set; }
}
declares an interface that contains one each of the possible kinds of members: A method, a property, an event, and an indexer.
An interface_declaration creates a new declaration space (Declarations), and the interface_member_declarations immediately contained by the interface_declaration introduce new members into this declaration space. The following rules apply to interface_member_declarations:
- The name of a method must differ from the names of all properties and events declared in the same interface. In addition, the signature (Signatures and overloading) of a method must differ from the signatures of all other methods declared in the same interface, and two methods declared in the same interface may not have signatures that differ solely by
ref
andout
. - The name of a property or event must differ from the names of all other members declared in the same interface.
- The signature of an indexer must differ from the signatures of all other indexers declared in the same interface.
The inherited members of an interface are specifically not part of the declaration space of the interface. Thus, an interface is allowed to declare a member with the same name or signature as an inherited member. When this occurs, the derived interface member is said to hide the base interface member. Hiding an inherited member is not considered an error, but it does cause the compiler to issue a warning. To suppress the warning, the declaration of the derived interface member must include a new
modifier to indicate that the derived member is intended to hide the base member. This topic is discussed further in Hiding through inheritance.
If a new
modifier is included in a declaration that doesn't hide an inherited member, a warning is issued to that effect. This warning is suppressed by removing the new
modifier.
Note that the members in class object
are not, strictly speaking, members of any interface (Interface members). However, the members in class object
are available via member lookup in any interface type (Member lookup).
Interface methods are declared using interface_method_declarations:
interface_method_declaration
: attributes? 'new'? return_type identifier type_parameter_list
'(' formal_parameter_list? ')' type_parameter_constraints_clause* ';'
;
The attributes, return_type, identifier, and formal_parameter_list of an interface method declaration have the same meaning as those of a method declaration in a class (Methods). An interface method declaration is not permitted to specify a method body, and the declaration therefore always ends with a semicolon.
Each formal parameter type of an interface method must be input-safe (Variance safety), and the return type must be either void
or output-safe. Furthermore, each class type constraint, interface type constraint and type parameter constraint on any type parameter of the method must be input-safe.
These rules ensure that any covariant or contravariant usage of the interface remains type-safe. For example,
interface I<out T> { void M<U>() where U : T; }
is illegal because the usage of T
as a type parameter constraint on U
is not input-safe.
Were this restriction not in place it would be possible to violate type safety in the following manner:
class B {}
class D : B{}
class E : B {}
class C : I<D> { public void M<U>() {...} }
...
I<B> b = new C();
b.M<E>();
This is actually a call to C.M<E>
. But that call requires that E
derive from D
, so type safety would be violated here.
Interface properties are declared using interface_property_declarations:
interface_property_declaration
: attributes? 'new'? type identifier '{' interface_accessors '}'
;
interface_accessors
: attributes? 'get' ';'
| attributes? 'set' ';'
| attributes? 'get' ';' attributes? 'set' ';'
| attributes? 'set' ';' attributes? 'get' ';'
;
The attributes, type, and identifier of an interface property declaration have the same meaning as those of a property declaration in a class (Properties).
The accessors of an interface property declaration correspond to the accessors of a class property declaration (Accessors), except that the accessor body must always be a semicolon. Thus, the accessors simply indicate whether the property is read-write, read-only, or write-only.
The type of an interface property must be output-safe if there is a get accessor, and must be input-safe if there is a set accessor.
Interface events are declared using interface_event_declarations:
interface_event_declaration
: attributes? 'new'? 'event' type identifier ';'
;
The attributes, type, and identifier of an interface event declaration have the same meaning as those of an event declaration in a class (Events).
The type of an interface event must be input-safe.
Interface indexers are declared using interface_indexer_declarations:
interface_indexer_declaration
: attributes? 'new'? type 'this' '[' formal_parameter_list ']' '{' interface_accessors '}'
;
The attributes, type, and formal_parameter_list of an interface indexer declaration have the same meaning as those of an indexer declaration in a class (Indexers).
The accessors of an interface indexer declaration correspond to the accessors of a class indexer declaration (Indexers), except that the accessor body must always be a semicolon. Thus, the accessors simply indicate whether the indexer is read-write, read-only, or write-only.
All the formal parameter types of an interface indexer must be input-safe . In addition, any out
or ref
formal parameter types must also be output-safe. Note that even out
parameters are required to be input-safe, due to a limitation of the underlying execution platform.
The type of an interface indexer must be output-safe if there is a get accessor, and must be input-safe if there is a set accessor.
Interface members are accessed through member access (Member access) and indexer access (Indexer access) expressions of the form I.M
and I[A]
, where I
is an interface type, M
is a method, property, or event of that interface type, and A
is an indexer argument list.
For interfaces that are strictly single-inheritance (each interface in the inheritance chain has exactly zero or one direct base interface), the effects of the member lookup (Member lookup), method invocation (Method invocations), and indexer access (Indexer access) rules are exactly the same as for classes and structs: More derived members hide less derived members with the same name or signature. However, for multiple-inheritance interfaces, ambiguities can occur when two or more unrelated base interfaces declare members with the same name or signature. This section shows several examples of such situations. In all cases, explicit casts can be used to resolve the ambiguities.
In the example
interface IList
{
int Count { get; set; }
}
interface ICounter
{
void Count(int i);
}
interface IListCounter: IList, ICounter {}
class C
{
void Test(IListCounter x) {
x.Count(1); // Error
x.Count = 1; // Error
((IList)x).Count = 1; // Ok, invokes IList.Count.set
((ICounter)x).Count(1); // Ok, invokes ICounter.Count
}
}
the first two statements cause compile-time errors because the member lookup (Member lookup) of Count
in IListCounter
is ambiguous. As illustrated by the example, the ambiguity is resolved by casting x
to the appropriate base interface type. Such casts have no run-time costs—they merely consist of viewing the instance as a less derived type at compile-time.
In the example
interface IInteger
{
void Add(int i);
}
interface IDouble
{
void Add(double d);
}
interface INumber: IInteger, IDouble {}
class C
{
void Test(INumber n) {
n.Add(1); // Invokes IInteger.Add
n.Add(1.0); // Only IDouble.Add is applicable
((IInteger)n).Add(1); // Only IInteger.Add is a candidate
((IDouble)n).Add(1); // Only IDouble.Add is a candidate
}
}
the invocation n.Add(1)
selects IInteger.Add
by applying the overload resolution rules of Overload resolution. Similarly the invocation n.Add(1.0)
selects IDouble.Add
. When explicit casts are inserted, there is only one candidate method, and thus no ambiguity.
In the example
interface IBase
{
void F(int i);
}
interface ILeft: IBase
{
new void F(int i);
}
interface IRight: IBase
{
void G();
}
interface IDerived: ILeft, IRight {}
class A
{
void Test(IDerived d) {
d.F(1); // Invokes ILeft.F
((IBase)d).F(1); // Invokes IBase.F
((ILeft)d).F(1); // Invokes ILeft.F
((IRight)d).F(1); // Invokes IBase.F
}
}
the IBase.F
member is hidden by the ILeft.F
member. The invocation d.F(1)
thus selects ILeft.F
, even though IBase.F
appears to not be hidden in the access path that leads through IRight
.
The intuitive rule for hiding in multiple-inheritance interfaces is simply this: If a member is hidden in any access path, it is hidden in all access paths. Because the access path from IDerived
to ILeft
to IBase
hides IBase.F
, the member is also hidden in the access path from IDerived
to IRight
to IBase
.
An interface member is sometimes referred to by its fully qualified name. The fully qualified name of an interface member consists of the name of the interface in which the member is declared, followed by a dot, followed by the name of the member. The fully qualified name of a member references the interface in which the member is declared. For example, given the declarations
interface IControl
{
void Paint();
}
interface ITextBox: IControl
{
void SetText(string text);
}
the fully qualified name of Paint
is IControl.Paint
and the fully qualified name of SetText
is ITextBox.SetText
.
In the example above, it is not possible to refer to Paint
as ITextBox.Paint
.
When an interface is part of a namespace, the fully qualified name of an interface member includes the namespace name. For example
namespace System
{
public interface ICloneable
{
object Clone();
}
}
Here, the fully qualified name of the Clone
method is System.ICloneable.Clone
.
Interfaces may be implemented by classes and structs. To indicate that a class or struct directly implements an interface, the interface identifier is included in the base class list of the class or struct. For example:
interface ICloneable
{
object Clone();
}
interface IComparable
{
int CompareTo(object other);
}
class ListEntry: ICloneable, IComparable
{
public object Clone() {...}
public int CompareTo(object other) {...}
}
A class or struct that directly implements an interface also directly implements all of the interface's base interfaces implicitly. This is true even if the class or struct doesn't explicitly list all base interfaces in the base class list. For example:
interface IControl
{
void Paint();
}
interface ITextBox: IControl
{
void SetText(string text);
}
class TextBox: ITextBox
{
public void Paint() {...}
public void SetText(string text) {...}
}
Here, class TextBox
implements both IControl
and ITextBox
.
When a class C
directly implements an interface, all classes derived from C also implement the interface implicitly. The base interfaces specified in a class declaration can be constructed interface types (Constructed types). A base interface cannot be a type parameter on its own, though it can involve the type parameters that are in scope. The following code illustrates how a class can implement and extend constructed types:
class C<U,V> {}
interface I1<V> {}
class D: C<string,int>, I1<string> {}
class E<T>: C<int,T>, I1<T> {}
The base interfaces of a generic class declaration must satisfy the uniqueness rule described in Uniqueness of implemented interfaces.
For purposes of implementing interfaces, a class or struct may declare explicit interface member implementations. An explicit interface member implementation is a method, property, event, or indexer declaration that references a fully qualified interface member name. For example
interface IList<T>
{
T[] GetElements();
}
interface IDictionary<K,V>
{
V this[K key];
void Add(K key, V value);
}
class List<T>: IList<T>, IDictionary<int,T>
{
T[] IList<T>.GetElements() {...}
T IDictionary<int,T>.this[int index] {...}
void IDictionary<int,T>.Add(int index, T value) {...}
}
Here IDictionary<int,T>.this
and IDictionary<int,T>.Add
are explicit interface member implementations.
In some cases, the name of an interface member may not be appropriate for the implementing class, in which case the interface member may be implemented using explicit interface member implementation. A class implementing a file abstraction, for example, would likely implement a Close
member function that has the effect of releasing the file resource, and implement the Dispose
method of the IDisposable
interface using explicit interface member implementation:
interface IDisposable
{
void Dispose();
}
class MyFile: IDisposable
{
void IDisposable.Dispose() {
Close();
}
public void Close() {
// Do what's necessary to close the file
System.GC.SuppressFinalize(this);
}
}
It is not possible to access an explicit interface member implementation through its fully qualified name in a method invocation, property access, or indexer access. An explicit interface member implementation can only be accessed through an interface instance, and is in that case referenced simply by its member name.
It is a compile-time error for an explicit interface member implementation to include access modifiers, and it is a compile-time error to include the modifiers abstract
, virtual
, override
, or static
.
Explicit interface member implementations have different accessibility characteristics than other members. Because explicit interface member implementations are never accessible through their fully qualified name in a method invocation or a property access, they are in a sense private. However, since they can be accessed through an interface instance, they are in a sense also public.
Explicit interface member implementations serve two primary purposes:
- Because explicit interface member implementations are not accessible through class or struct instances, they allow interface implementations to be excluded from the public interface of a class or struct. This is particularly useful when a class or struct implements an internal interface that is of no interest to a consumer of that class or struct.
- Explicit interface member implementations allow disambiguation of interface members with the same signature. Without explicit interface member implementations it would be impossible for a class or struct to have different implementations of interface members with the same signature and return type, as would it be impossible for a class or struct to have any implementation at all of interface members with the same signature but with different return types.
For an explicit interface member implementation to be valid, the class or struct must name an interface in its base class list that contains a member whose fully qualified name, type, and parameter types exactly match those of the explicit interface member implementation. Thus, in the following class
class Shape: ICloneable
{
object ICloneable.Clone() {...}
int IComparable.CompareTo(object other) {...} // invalid
}
the declaration of IComparable.CompareTo
results in a compile-time error because IComparable
is not listed in the base class list of Shape
and is not a base interface of ICloneable
. Likewise, in the declarations
class Shape: ICloneable
{
object ICloneable.Clone() {...}
}
class Ellipse: Shape
{
object ICloneable.Clone() {...} // invalid
}
the declaration of ICloneable.Clone
in Ellipse
results in a compile-time error because ICloneable
is not explicitly listed in the base class list of Ellipse
.
The fully qualified name of an interface member must reference the interface in which the member was declared. Thus, in the declarations
interface IControl
{
void Paint();
}
interface ITextBox: IControl
{
void SetText(string text);
}
class TextBox: ITextBox
{
void IControl.Paint() {...}
void ITextBox.SetText(string text) {...}
}
the explicit interface member implementation of Paint
must be written as IControl.Paint
.
The interfaces implemented by a generic type declaration must remain unique for all possible constructed types. Without this rule, it would be impossible to determine the correct method to call for certain constructed types. For example, suppose a generic class declaration were permitted to be written as follows:
interface I<T>
{
void F();
}
class X<U,V>: I<U>, I<V> // Error: I<U> and I<V> conflict
{
void I<U>.F() {...}
void I<V>.F() {...}
}
Were this permitted, it would be impossible to determine which code to execute in the following case:
I<int> x = new X<int,int>();
x.F();
To determine if the interface list of a generic type declaration is valid, the following steps are performed:
- Let
L
be the list of interfaces directly specified in a generic class, struct, or interface declarationC
. - Add to
L
any base interfaces of the interfaces already inL
. - Remove any duplicates from
L
. - If any possible constructed type created from
C
would, after type arguments are substituted intoL
, cause two interfaces inL
to be identical, then the declaration ofC
is invalid. Constraint declarations are not considered when determining all possible constructed types.
In the class declaration X
above, the interface list L
consists of I<U>
and I<V>
. The declaration is invalid because any constructed type with U
and V
being the same type would cause these two interfaces to be identical types.
It is possible for interfaces specified at different inheritance levels to unify:
interface I<T>
{
void F();
}
class Base<U>: I<U>
{
void I<U>.F() {...}
}
class Derived<U,V>: Base<U>, I<V> // Ok
{
void I<V>.F() {...}
}
This code is valid even though Derived<U,V>
implements both I<U>
and I<V>
. The code
I<int> x = new Derived<int,int>();
x.F();
invokes the method in Derived
, since Derived<int,int>
effectively re-implements I<int>
(Interface re-implementation).
When a generic method implicitly implements an interface method, the constraints given for each method type parameter must be equivalent in both declarations (after any interface type parameters are replaced with the appropriate type arguments), where method type parameters are identified by ordinal positions, left to right.
When a generic method explicitly implements an interface method, however, no constraints are allowed on the implementing method. Instead, the constraints are inherited from the interface method
interface I<A,B,C>
{
void F<T>(T t) where T: A;
void G<T>(T t) where T: B;
void H<T>(T t) where T: C;
}
class C: I<object,C,string>
{
public void F<T>(T t) {...} // Ok
public void G<T>(T t) where T: C {...} // Ok
public void H<T>(T t) where T: string {...} // Error
}
The method C.F<T>
implicitly implements I<object,C,string>.F<T>
. In this case, C.F<T>
is not required (nor permitted) to specify the constraint T:object
since object
is an implicit constraint on all type parameters. The method C.G<T>
implicitly implements I<object,C,string>.G<T>
because the constraints match those in the interface, after the interface type parameters are replaced with the corresponding type arguments. The constraint for method C.H<T>
is an error because sealed types (string
in this case) cannot be used as constraints. Omitting the constraint would also be an error since constraints of implicit interface method implementations are required to match. Thus, it is impossible to implicitly implement I<object,C,string>.H<T>
. This interface method can only be implemented using an explicit interface member implementation:
class C: I<object,C,string>
{
...
public void H<U>(U u) where U: class {...}
void I<object,C,string>.H<T>(T t) {
string s = t; // Ok
H<T>(t);
}
}
In this example, the explicit interface member implementation invokes a public method having strictly weaker constraints. Note that the assignment from t
to s
is valid since T
inherits a constraint of T:string
, even though this constraint is not expressible in source code.
A class or struct must provide implementations of all members of the interfaces that are listed in the base class list of the class or struct. The process of locating implementations of interface members in an implementing class or struct is known as interface mapping.
Interface mapping for a class or struct C
locates an implementation for each member of each interface specified in the base class list of C
. The implementation of a particular interface member I.M
, where I
is the interface in which the member M
is declared, is determined by examining each class or struct S
, starting with C
and repeating for each successive base class of C
, until a match is located:
- If
S
contains a declaration of an explicit interface member implementation that matchesI
andM
, then this member is the implementation ofI.M
. - Otherwise, if
S
contains a declaration of a non-static public member that matchesM
, then this member is the implementation ofI.M
. If more than one member matches, it is unspecified which member is the implementation ofI.M
. This situation can only occur ifS
is a constructed type where the two members as declared in the generic type have different signatures, but the type arguments make their signatures identical.
A compile-time error occurs if implementations cannot be located for all members of all interfaces specified in the base class list of C
. Note that the members of an interface include those members that are inherited from base interfaces.
For purposes of interface mapping, a class member A
matches an interface member B
when:
A
andB
are methods, and the name, type, and formal parameter lists ofA
andB
are identical.A
andB
are properties, the name and type ofA
andB
are identical, andA
has the same accessors asB
(A
is permitted to have additional accessors if it is not an explicit interface member implementation).A
andB
are events, and the name and type ofA
andB
are identical.A
andB
are indexers, the type and formal parameter lists ofA
andB
are identical, andA
has the same accessors asB
(A
is permitted to have additional accessors if it is not an explicit interface member implementation).
Notable implications of the interface mapping algorithm are:
- Explicit interface member implementations take precedence over other members in the same class or struct when determining the class or struct member that implements an interface member.
- Neither non-public nor static members participate in interface mapping.
In the example
interface ICloneable
{
object Clone();
}
class C: ICloneable
{
object ICloneable.Clone() {...}
public object Clone() {...}
}
the ICloneable.Clone
member of C
becomes the implementation of Clone
in ICloneable
because explicit interface member implementations take precedence over other members.
If a class or struct implements two or more interfaces containing a member with the same name, type, and parameter types, it is possible to map each of those interface members onto a single class or struct member. For example
interface IControl
{
void Paint();
}
interface IForm
{
void Paint();
}
class Page: IControl, IForm
{
public void Paint() {...}
}
Here, the Paint
methods of both IControl
and IForm
are mapped onto the Paint
method in Page
. It is of course also possible to have separate explicit interface member implementations for the two methods.
If a class or struct implements an interface that contains hidden members, then some members must necessarily be implemented through explicit interface member implementations. For example
interface IBase
{
int P { get; }
}
interface IDerived: IBase
{
new int P();
}
An implementation of this interface would require at least one explicit interface member implementation, and would take one of the following forms
class C: IDerived
{
int IBase.P { get {...} }
int IDerived.P() {...}
}
class C: IDerived
{
public int P { get {...} }
int IDerived.P() {...}
}
class C: IDerived
{
int IBase.P { get {...} }
public int P() {...}
}
When a class implements multiple interfaces that have the same base interface, there can be only one implementation of the base interface. In the example
interface IControl
{
void Paint();
}
interface ITextBox: IControl
{
void SetText(string text);
}
interface IListBox: IControl
{
void SetItems(string[] items);
}
class ComboBox: IControl, ITextBox, IListBox
{
void IControl.Paint() {...}
void ITextBox.SetText(string text) {...}
void IListBox.SetItems(string[] items) {...}
}
it is not possible to have separate implementations for the IControl
named in the base class list, the IControl
inherited by ITextBox
, and the IControl
inherited by IListBox
. Indeed, there is no notion of a separate identity for these interfaces. Rather, the implementations of ITextBox
and IListBox
share the same implementation of IControl
, and ComboBox
is simply considered to implement three interfaces, IControl
, ITextBox
, and IListBox
.
The members of a base class participate in interface mapping. In the example
interface Interface1
{
void F();
}
class Class1
{
public void F() {}
public void G() {}
}
class Class2: Class1, Interface1
{
new public void G() {}
}
the method F
in Class1
is used in Class2
's implementation of Interface1
.
A class inherits all interface implementations provided by its base classes.
Without explicitly re-implementing an interface, a derived class cannot in any way alter the interface mappings it inherits from its base classes. For example, in the declarations
interface IControl
{
void Paint();
}
class Control: IControl
{
public void Paint() {...}
}
class TextBox: Control
{
new public void Paint() {...}
}
the Paint
method in TextBox
hides the Paint
method in Control
, but it does not alter the mapping of Control.Paint
onto IControl.Paint
, and calls to Paint
through class instances and interface instances will have the following effects
Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint(); // invokes Control.Paint();
t.Paint(); // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes Control.Paint();
However, when an interface method is mapped onto a virtual method in a class, it is possible for derived classes to override the virtual method and alter the implementation of the interface. For example, rewriting the declarations above to
interface IControl
{
void Paint();
}
class Control: IControl
{
public virtual void Paint() {...}
}
class TextBox: Control
{
public override void Paint() {...}
}
the following effects will now be observed
Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint(); // invokes Control.Paint();
t.Paint(); // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes TextBox.Paint();
Since explicit interface member implementations cannot be declared virtual, it is not possible to override an explicit interface member implementation. However, it is perfectly valid for an explicit interface member implementation to call another method, and that other method can be declared virtual to allow derived classes to override it. For example
interface IControl
{
void Paint();
}
class Control: IControl
{
void IControl.Paint() { PaintControl(); }
protected virtual void PaintControl() {...}
}
class TextBox: Control
{
protected override void PaintControl() {...}
}
Here, classes derived from Control
can specialize the implementation of IControl.Paint
by overriding the PaintControl
method.
A class that inherits an interface implementation is permitted to re-implement the interface by including it in the base class list.
A re-implementation of an interface follows exactly the same interface mapping rules as an initial implementation of an interface. Thus, the inherited interface mapping has no effect whatsoever on the interface mapping established for the re-implementation of the interface. For example, in the declarations
interface IControl
{
void Paint();
}
class Control: IControl
{
void IControl.Paint() {...}
}
class MyControl: Control, IControl
{
public void Paint() {}
}
the fact that Control
maps IControl.Paint
onto Control.IControl.Paint
doesn't affect the re-implementation in MyControl
, which maps IControl.Paint
onto MyControl.Paint
.
Inherited public member declarations and inherited explicit interface member declarations participate in the interface mapping process for re-implemented interfaces. For example
interface IMethods
{
void F();
void G();
void H();
void I();
}
class Base: IMethods
{
void IMethods.F() {}
void IMethods.G() {}
public void H() {}
public void I() {}
}
class Derived: Base, IMethods
{
public void F() {}
void IMethods.H() {}
}
Here, the implementation of IMethods
in Derived
maps the interface methods onto Derived.F
, Base.IMethods.G
, Derived.IMethods.H
, and Base.I
.
When a class implements an interface, it implicitly also implements all of that interface's base interfaces. Likewise, a re-implementation of an interface is also implicitly a re-implementation of all of the interface's base interfaces. For example
interface IBase
{
void F();
}
interface IDerived: IBase
{
void G();
}
class C: IDerived
{
void IBase.F() {...}
void IDerived.G() {...}
}
class D: C, IDerived
{
public void F() {...}
public void G() {...}
}
Here, the re-implementation of IDerived
also re-implements IBase
, mapping IBase.F
onto D.F
.
Like a non-abstract class, an abstract class must provide implementations of all members of the interfaces that are listed in the base class list of the class. However, an abstract class is permitted to map interface methods onto abstract methods. For example
interface IMethods
{
void F();
void G();
}
abstract class C: IMethods
{
public abstract void F();
public abstract void G();
}
Here, the implementation of IMethods
maps F
and G
onto abstract methods, which must be overridden in non-abstract classes that derive from C
.
Note that explicit interface member implementations cannot be abstract, but explicit interface member implementations are of course permitted to call abstract methods. For example
interface IMethods
{
void F();
void G();
}
abstract class C: IMethods
{
void IMethods.F() { FF(); }
void IMethods.G() { GG(); }
protected abstract void FF();
protected abstract void GG();
}
Here, non-abstract classes that derive from C
would be required to override FF
and GG
, thus providing the actual implementation of IMethods
.