20.1.6 static constructors in generic classes
Static constructors in generic classes are used to initialize static fields, performing additional initialization for each of the different enclosing constructed types created from a particular generic class declaration. The type parameter of a generic type declaration is within scope and can be used in the static constructor body.
A new enclosing constructed class type will be initialized for the first time if one of the following conditions occurs.
When an instance of an enclosing constructed type is created
When any static member of a enclosing constructed type is referenced
To initialize a new enclosing constructed class type, the first set of new static fields (§20.1.5) for that particular enclosing type will be created. Each static field is initialized to its default value (§5.2). The static field initializers (§10.4.5.1) are then executed for these static fields. The last static constructor is executed.
Because static constructors are executed once for each enclosing constructed class type, it is convenient to implement run-time checks on type parameters that cannot be checked by constraint (§20.7). For example, the following type uses a static constructor to check whether a type parameter is a reference type.
class Gen<T>
{
static Gen(){
if((object)T.default != null){
throw new ArgumentException(“T must be a reference type”);
}
}
}
20.1.7 access to Protected members
In a generic class declaration, access to inherited protected instance members is allowed, by any type of instance constructed from a generic class. In particular, the rules used to access the protected and protected internal instance members specified in §3.5.3 are expanded for generics using the following rules.
In a generic class G, for an inherited protected instance member M, a basic expression using E.M is allowed, provided that the type of E is a class type constructed from G, or a class type that inherits from a class type constructed from G.
In the example
class C<T>
{
protected T x;
}
class D<T> :C<T>
{
static void F(){
D<T> dt = new D<T>();
D<int> di = new D<int>();
D<string> ds = new D<string>();
dt.x = T.default;
di.x = 123;
ds.x = “test”;
}
}
Three assignment statements to X are allowed because they all occur through an instance of a class type constructed from a generic type.
20.1.8 Overloading in generic classes
methods, constructors, indexers, and operators in a generic class declaration can be overloaded. However, in order to avoid ambiguity in the construction class, these overloads are constrained. Two function members declared with the same name in the same generic class declaration must have such a parameter type, that is, the enclosing constructed type cannot appear in two members using the same name and signature. When all possible enclosing constructed types are considered, this rule contains the arguments that are not currently present in the current program, but it is still possible [1]. Type constraints on type parameters are ignored because of the purpose of this rule.
The following example shows a valid and invalid overload based on this rule.
Nterface I1<T> {...}
Interface I2<T>{...}
Class G1<U>
{
Long F1(U u); // invalid overload, G<int> will have two members with the same signature
Int F1(int i);
Void F2(U u1, U u2); //Valid overload, no type parameter for U
Void F2(int I , string s); //may be both int and string
Void F3 (I1<U>a); //effective overload
Void F3(I2<U>a);
Void F4(U a); //effective overload
Void F4(U[] a);}
Class G2<U,V>
{
Void F5(U u , V v); // invalid overload, G2<int , int> will have two members with the same signature
Void F5(V v, U u);
Void F6(U u , I1<V> v);//Invalid overload, G2<I1<int>, int> will have two members with the same signature
Void F6(I1<V> v , U u);
Void F7(U u1,I1<V> V2);//Effective overload, U cannot be both V and I1<V>
Void F7(V v1 , U u2);
Void F8(ref U u); // invalid overload
Void F8(out V v);
}
Class C1{...}
Class C2{...}
Class G3<U , V> where U:C1 where V:C2
{
Void F9(U u); // invalid overload, constraints on U and V will be ignored when checking for overloads
Void F9(V v);
}
0.1.9 parameter array methods and type parameters
Type parameters can be used in the type of the parameter array. For example, given a declaration
Class C<V>
{
Static void F(int x, int y ,params V[] args);
}
The following form of the extended form of the method
C<int>.F(10, 20);
C<object>.F(10,20,30,40);
C<string>.F(10,20,"hello","goodbye");
Corresponds to the following form:
C<int>.F(10,20, new int[]{});
C<object>.F(10,20,new object[]{30,40});
C<string>.F(10,20,new string[]("hello","goodbye"));
20.1.10 Overrides and generic classes
A function member in a generic class can override a function member in a base class. If the base class is a non-generic type or an enclosing constructed type, then any overriding function member cannot have a constituent type that contains a type parameter. However, if a base class is an open constructed type, then the overriding function member can use the type parameter in its declaration. When overriding a base class member, the base class member must be determined by replacing the type argument, as described in §20.5.4. Once the members of the base class are determined, the rules used for overriding and non-generic classes are the same.
The following example shows how the override rules for an existing generic are working.
Abstract class C<t>{public Virtual T F () {...} Public virtual c<t> G () {...} public virtual void H (c<t> x) {...}} Class D:c<string>{public override string F () {...} Okpublic override C<string> G () {...} Okpublic override void H (c<t> x); Error, should be C<string>}class e<t,u>:c<u>{public override U F () {...} Okpublic override C<u> G () {...} Okpublic override void H (C<t> x) {...} Error, should be c<u>}
20.1.11 operators in generic classes
Generic class declarations can define operators, which follow the same rules as regular classes. The instance type of the class declaration (§20.1.2) must be used in an operator declaration in a manner similar to the general rules of the operator, as follows
A unary operator must accept a single argument of an instance type. The unary operator "+ +" and "-" must return an instance type.
At least one of the arguments for the two-tuple operator must be an instance type.
Both the parameter type and the return type of the conversion operator must be of the instance type.
The following shows an example of several valid operator declarations in a generic class
Abstract class C<T>
{
Public virtual T F(){...}
Public virtual C<T> G(){...}
Public virtual void H(C<T> x ){...}
}
Class D:C<string>
{
Public override string F(){...}//OK
Public override C<string> G(){...}//OK
Public override void H(C<T> x); //Error, it should be C<string>
}
Class E<T,U>:C<U>
{
Public override U F(){...}//OK
Public override C<U> G(){...}//OK
Public override void H(C<T> x){...}//Error, it should be C<U>
}
For a conversion operator from the source type S to the target type T, when a rule in §10.9.3 is applied, any type parameters of the associated s or T are considered to be unique types, they have no inheritance relationship with other types, and any constraints on those type parameters are ignored.
In the example
class X<T>
{
public static X<T> operator ++(X(T) operand){…}
public static int operator *(X<T> op1, int op2){…}
public static explicit operator X<T>(T value){…}
}
The first operator declaration is allowed, and because of §10.9.3, T and int are considered to be the only types that have no relationship. However, the second operator is an error because c<t> is the base class for d<t>.
Given the previous example, it is possible to declare operators for some type arguments, specifying conversions that already exist as predefined transformations.
struct Nullable<T>
{
public static implicit operator Nullable<T>(T value){…}
public static explicit operator T(Nullable<T> value){…}
}
When type object is specified as the type argument of T, the second operator declares an already existing transformation (from any type to object is an implicit or explicit conversion).
In the case of a predefined conversion between two types, any user-defined conversions on those types are ignored. Especially
If there is a predefined implicit conversion (§6.1) from type S to type T, all user-defined conversions (implicit or explicit) are ignored.
If there is a predefined explicit conversion from type S to type T, any user-defined explicit conversions from type S to type T are ignored. But user-defined implicit conversions from S to T are still considered.
For all types except object, operators declared by the nullable<t> type do not conflict with predefined conversions. For example
Void F(int I , Nullable<int> n){
i = n; //error
i = (int)n; //user-defined explicit conversion
n = i; //user-defined implicit conversion
n = (Nullable<int>)i; //user-defined implicit conversion
}
However, for type object, the predefined transformations hide the user-defined conversions in all cases except in one case:
Void F(object o , Nullable<object> n){
o = n; //predefined boxing conversion
o= (object)n; //Predefined boxing conversion
n= o; //user-defined implicit conversion
n = (Nullable<object>)o; //Predefined unboxing conversion
}
20.1.12 nested types in generic classes
Generic class declarations can contain nested type declarations. The type parameters of the enclosing class can be used in nested types. A nested type declaration can contain additional type parameters, which apply only to that nested type.
Each type declaration contained in a generic class declaration is implicitly a generic type declaration. When you write a reference to a type that is nested within a generic type, the containing constructed type, including its type arguments, must be named. However, in an external class, an internal type can be used indefinitely, and an instance type of an external class can be implicitly used when constructing an internal type. The following example shows three different methods of referencing constructed types created from inner, all of which are correct; the first two are equivalent.
Class Outer<T>
{
Class Inner<U>
{
Static void F(T t , U u){...}
}
Static void F(T t)
{
Outer<T>.Inner<string >.F(t,"abc");//These two statements have the same effect
Inner<string>.F(t,"abc");
Outer<int>.Inner<string>.F(3,"abc"); //This type is different
Outer.Inner<string>.F(t , "abc"); //Error, Outer needs type parameter
}
}
Although this is a bad programming style, a type parameter in a nested type can hide a member, or a type parameter declared in an external type.
Class Outer<T>
{
Class Inner<T> //valid, hides Ouer's T
{
Public T t; //refer to Inner's T
}
}
20.1.13 Application Entry point
The application entry point cannot be in a generic class declaration.
20.2 generic struct declarations
Like a class declaration, a struct declaration can have an optional type parameter.
Struct-declaration: (structural declaration:)
Attribute opt struct-modifiers opt struct identifier type-parameter-list opt struct-interfaces opt type-parameter-constraints-clauses opt struct-body ;opt
(Feature optional Structure modifier optional struct identifier Type parameter list optional Structure interface optional Type parameter constraint statement optional Structure; optional)
In addition to the differences noted in §11.3 for struct declarations, the rules for generic class declarations also apply to generic struct declarations.
20.3 generic interface declarations
An interface can also define optional type parameters
Interface-declaration: (interface declaration:)
Attribute opt interface-modifiers opt interface indentifier type-parameter-list opt
Interface-base opt type-parameter-constraints-clause opt interface-body;
(Feature optional interface modifier optional interface identifier Type parameter list optional Base interface optional Type parameter constraint statement optional Interface body; optional)
An interface declared with a type parameter is a generic interface declaration. In addition to those indicated, generic interface declarations follow the same rules as regular structure declarations.
Each type parameter in the interface declaration defines a name in the declaration space of the interface. The scope of a type parameter on an interface includes the base interface, type constraint statement, and interface body. Within its scope, a type parameter can be used as a type. The type parameters applied to the interface and the type parameters applied to the class (§20.1.1) have the same restrictions.
Methods in a generic interface follow the same overloaded rules as methods in a generic class (§20.1.8).
20.3.1 to implement the uniqueness of the interface
An interface implemented by a generic type declaration must retain uniqueness for all possible construction types. Without this rule, it is not possible to determine the correct method to invoke for a particular constructed type. For example, suppose a generic class declaration allows the following notation.
Interface I<T>
{
Void F();
}
Class X<U, V>:I<U>, I<V> //Error, I<U> and I<V> conflicts
{
Void I<U>.F(){...}
Void I<V>.F(){...}
}
If this is allowed, the following scenario will not be able to determine the execution of that code.
i<int> x = new X<int,int> (); X.f ();
In order to determine the list of interfaces for a generic type declaration to be valid, you can proceed as follows.
Let L be the list of interfaces specified in the generic class, struct, or interface declaration C.
Add any base interfaces that are already in the L interface to the L
Remove any duplicate interfaces from L
After a type argument is replaced with L, if any of the possible constructed types created from C cause the two interfaces in L to be the same, then the declaration of C is invalid. Constraint declarations are not considered when all possible construction types are determined.
Above the class declaration X, the interface list L consists of i<u> and i<v>. The declaration is invalid because any constructed type that uses the same type U and V will cause the two interfaces to be the same.
20.3.2 explicit interface member implementations
Explicit interface member implementations that use constructed interface types are essentially the same way as simple interface types. As always, an explicit interface member implementation must be qualified by an interface type that indicates which interface is implemented. The type may be a simple interface or a construction interface, as shown in the following example.
interface IList<T>
{
T[] GetElement();
}
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>.GetElement(){…}
T IDictionary<int , T>.this[int index]{…}
void IDictionary<int , T>.Add(int index , T value){…}
}
[1] That is, when the type parameter is replaced with a type argument, it is possible to replace the actual parameter resulting in two members using the same name and signature.
The above is c#2.0 specification (generic II) content, more relevant content please pay attention to topic.alibabacloud.com (www.php.cn)!