Contents
Generics and the .NET Framework
l When deriving from a generic base class, you must provide a concrete type argument instead of the base-class's generic type parameter:
派生类不是泛型类,则需要提供具体化类型。
public class BaseClass<T>
{...}
public class SubClass : BaseClass<int>
{...}
l If the subclass is generic, instead of a concrete type argument, you can use the subclass generic type parameter as the specified type for the generic base class(有点拗口,但make sense):
派生类也是泛型类,则可以用泛型类型传导
public class SubClass<T> : BaseClass<T>
{...}
l When using the subclass generic type parameters, you must repeat any constraints stipulated at the base class level at the subclass level. For example, derivation constraint:
基类的约束,派生类必须重复一遍
public class BaseClass<T> where T : ISomeInterface
{...}
public class SubClass<T> : BaseClass<T> where T : ISomeInterface
{...}
Or constructor constraint:
public class BaseClass<T> where T : new()
{
public T SomeMethod()
{
return new T();
}
}
public class SubClass<T> : BaseClass<T> where T : new()
{...}
l A base class can define virtual methods whose signatures use generic type parameters. When overriding them, the subclass must provide the corresponding types in the method signatures:
public class BaseClass<T>
{
public virtual T SomeMethod()
{...}
}
public class SubClass: BaseClass<int>
{
public override int SomeMethod()
{...}
}
If the subclass is generic it can also use its own generic type parameters for the override:
public class SubClass<T>: BaseClass<T>
{
public override T SomeMethod()
{...}
}
l You can define generic interfaces, generic abstract classes, and even generic abstract methods. These types behave like any other generic base type:
public interface ISomeInterface<T>
{
T SomeMethod(T t);
}
public abstract class BaseClass<T>
{
public abstract T SomeMethod(T t);
}
public class SubClass<T> : BaseClass<T>
{
public override T SomeMethod(T t)
{...)
}
l There is an interesting use for generic abstract methods and generic interfaces. In C# 2.0, it is impossible to use operators such as + or += on generic type parameters. For example, the following code does NOT compile because C# 2.0 does not have operator constraints:
public class Calculator<T>
{
public T Add(T arg1,T arg2)
{
return arg1 + arg2;//Does not compile
}
//Rest of the methods
}
Workaround 1---
Nonetheless, you can compensate using abstract methods (or preferably interfaces) by defining generic operations. Since an abstract method cannot have any code in it, you can specify the generic operations at the base class level, and provide a concrete type and implementation at the subclass level:
public abstract class BaseCalculator<T>
{
public abstract T Add(T arg1,T arg2);
public abstract T Subtract(T arg1,T arg2);
public abstract T Divide(T arg1,T arg2);
public abstract T Multiply(T arg1,T arg2);
}
public class MyCalculator : BaseCalculator<int>
{
public override int Add(int arg1, int arg2)
{
return arg1 + arg2;
}
//Rest of the methods
}
Workaround 2---A generic interface will yield a somewhat cleaner solution as well:
public interface ICalculator<T>
{
T Add(T arg1,T arg2);
//Rest of the methods
}
public class MyCalculator : ICalculator<int>
{
public int Add(int arg1, int arg2)
{
return arg1 + arg2;
}
//Rest of the methods
}
public class MyClass<T>
{
public void MyMethod<X>(X x)
{.
//In C# 2.0, a method can define generic type parameters, specific to its execution scope
}
}
This is an important capability because it allows you to call the method with a different type every time, which is very handy for utility classes.
l You can define method-specific generic type parameters even if the containing class does not use generics at all:
public class MyClass
{
public void MyMethod<T>(T t)
{...}
}
Note: This ability is for methods only. Properties or indexers can only use generic type parameters defined at the scope of the class.
When calling a method that defines generic type parameters, you can provide the type to use at the call site:
MyClass obj = new MyClass();
obj.MyMethod<int>(3);
C# compiler is smart enough to infer the correct type based on the type of parameter passed in, it allows omitting the type specification altogether(省略类型规范<int>):
MyClass obj = new MyClass();
obj.MyMethod(3);
This ability is called generic type inference(泛型类型推断).
Note that the compiler cannot infer the type based on the type of the returned value alone:
public class MyClass
{
public T MyMethod<T>()
{}
}
MyClass obj = new MyClass();
int number = obj.MyMethod();//Does not compile
When a method defines its own generic type parameters, it can also define constraints for these types:
public class MyClass
{
public void SomeMethod<T>(T t) where T : IComparable<T>
{...}
}
然而, 你不能为class-level generic type parameters提供method-level constraints.
All constraints for class-level generic type parameters must be defined at the class scope.
When overriding a virtual method that defines generic type parameters, the subclass method must redefine the method-specific generic type parameter:
public class BaseClass
{
public virtual void SomeMethod<T>(T t)
{...}
}
public class SubClass : BaseClass
{
public override void SomeMethod<T>(T t)
{...}
}
The subclass implementation must repeat all constraints that appeared at the base method level:
public class BaseClass
{
public virtual void SomeMethod<T>(T t) where T : new()
{...}
}
public class SubClass : BaseClass
{
public override void SomeMethod<T>(T t) where T : new()
{...}
}
Note that the method override 不能定义新的constraint;
In addition, if the subclass method calls the base class implementation of the virtual method, it must specify the type argument to use instead of the generic base method type parameter. You can either explicitly specify it yourself or rely on type inference if it is available:
public class BaseClass
{
public virtual void SomeMethod<T>(T t)
{...}
}
public class SubClass : BaseClass
{
public override void SomeMethod<T>(T t)
{
base.SomeMethod<T>(t);
base.SomeMethod(t);
}
}
public class MyClass<T>
{
public static T SomeMethod(T t)
{...}
}
int number = MyClass<int>.SomeMethod(3);
Static methods can define method-specific generic type parameters and constraints, similar to instance methods. When calling such methods, you need to provide the method-specific types at the call site, either explicitly:
public class MyClass<T>
{
public static T SomeMethod<X>(T t,X x)
{..}
}
int number = MyClass<int>.SomeMethod<string>(3,"AAA");
Or rely on type inference when possible:
int number = MyClass<int>.SomeMethod(3,"AAA");
Generic static methods are subjected to all constraints imposed on the generic type parameter they use at the class level. As with instance method, you can provide constraints for generic type parameters defined by the static method:
public class MyClass
{
public static T SomeMethod<T>(T t) where T : IComparable<T>
{...}
}
C# 中的运算符只是静态方法而已,并且 C# 允许您为自己的泛型类重载运算符。
Imagine that the generic LinkedList provided the + operator for concatenating linked lists. The + operator enables you to write the following elegant code:
LinkedList<int,string> list1 = new LinkedList<int,string>();
LinkedList<int,string> list2 = new LinkedList<int,string>();
...
LinkedList<int,string> list3 = list1+list2;
Code block 7 shows the implementation of the generic + operator on the LinkedList class. Note that operators cannot define new generic type parameters.
Code block 7. Implementing a generic operator
public class LinkedList<K,T>
{
public static LinkedList<K,T> operator+(LinkedList<K,T> lhs,
LinkedList<K,T> rhs)
{
return concatenate(lhs,rhs);
}
static LinkedList<K,T> concatenate(LinkedList<K,T> list1,
LinkedList<K,T> list2)
{
LinkedList<K,T> newList = new LinkedList<K,T>();
Node<K,T> current;
current = list1.m_Head;
while(current != null)
{
newList.AddHead(current.Key,current.Item);
current = current.NextNode;
}
current = list2.m_Head;
while(current != null)
{
newList.AddHead(current.Key,current.Item);
current = current.NextNode;
}
return newList;
}
//Rest of LinkedList
}
public class MyClass<T>
{
public delegate void GenericDelegate(T t);
public void SomeMethod(T t)
{...}
}
MyClass<int>.GenericDelegate del;
del = new MyClass<int>.GenericDelegate(obj.SomeMethod);
del(3);
C# 2.0 allows you to make a direct assignment of a method reference into a delegate variable:
MyClass<int> obj = new MyClass<int>();
MyClass<int>.GenericDelegate del = obj.SomeMethod;
I am calling this feature delegate inference.
The compiler is capable of inferring the type of the delegate you assign into, finding if the target object has a method by the name you specify(has method SomeMethod),
and verifying that the method's signature matches. Then, the compiler creates a new delegate of the inferred argument type(including the correct type instead of the generic type parameter), (new MyClass<int>.GenericDelegate(obj.SomeMethod)
and assigns the new delegate into the inferred delegate.
Code block . Generic event handling
public delegate void GenericEventHandler<S,A>(S sender,A args);
public class MyPublisher
{
public event GenericEventHandler<MyPublisher,EventArgs> MyEvent;
public void FireEvent()
{
MyEvent(this,EventArgs.Empty);
}
}
public class MySubscriber<A> //Optional: can be a specific type
{
public void SomeMethod(MyPublisher sender,A args)
{...}
}
MyPublisher publisher = new MyPublisher();
MySubscriber<EventArgs> subscriber = new MySubscriber<EventArgs>();
publisher.MyEvent += subscriber.SomeMethod;
Obviously, if you need more parameters, you can simply add more generic type parameters, 但是我想模仿按如下方式定义的 .NET EventHandler 来设计 GenericEventHandler::
public void delegate EventHandler(object sender,EventArgs args);
不像 EventHandler, GenericEventHandler 是类型安全的, as shown in Code block.
Because it accepts only objects of the type MyPublisher as senders, rather than mere Object. In fact, .NET already defines a generic flavor of EventHandler in the System namespace:
public void delegate EventHandler(object sender,A args) where A : EventArgs;
C#2.0中,反射对泛型的支持:类型 Type 现在可以表示带有特定类型实参(称为绑定类型)或未指定(未绑定)类型的一般类型。
使用 typeof 运算符或者通过调用每个类型支持的 GetType() 方法来获得任何类型的 Type。不管您选择哪种方式,都会产生相同的 Type
LinkedList<int,string> list = new LinkedList<int,string>();
Type type1 = typeof(LinkedList<int,string>);
Type type2 = list.GetType();
Debug.Assert(type1 == type2);
typeof 和 GetType() 都可以对一般类型参数进行操作:
public class MyClass
{
public void SomeMethod(T t)
{
Type type = typeof(T);
Debug.Assert(type == t.GetType());
}
}
此外,typeof 运算符还可以对未绑定的一般类型进行操作。例如:
public class MyClass<T>
{}
Type unboundedType = typeof(MyClass<>);
Trace.WriteLine(unboundedType.ToString());
//Writes: MyClass`1[T]
所追踪的数字 1 是所使用的一般类型的一般类型参数的数量。请注意空 <> 的用法。要对带有多个类型参数的未绑定一般类型进行操作,请在 <> 中使用“,”:
public class LinkedList<K,T>
{...}
Type unboundedList = typeof(LinkedList<,>);
Trace.WriteLine(unboundedList.ToString());
//Writes: LinkedList`2[K,T]
Type 具有新的方法和属性,用于提供有关该类型的一般方面的反射信息
Using Type for generic reflection
LinkedList<int,string> list = new LinkedList<int,string>();
Type boundedType = list.GetType();
Trace.WriteLine(boundedType.ToString());
//Writes: LinkedList`2[System.Int32,System.String]
Debug.Assert(boundedType.HasGenericArguments);
Type[] parameters = boundedType.GetGenericArguments();
Debug.Assert(parameters.Length == 2);
Debug.Assert(parameters[0] == typeof(int));
Debug.Assert(parameters[1] == typeof(string));
Type unboundedType = boundedType.GetGenericTypeDefinition();
Debug.Assert(unboundedType == typeof(LinkedList<,>));
Trace.WriteLine(unboundedType.ToString());
//Writes: LinkedList`2[K,T]
As in C# 1.1, you can use MethodInfo (as well as a number of other options) for late binding invocation. However, the type of the parameters you pass for the late binding must match the bounded types used instead of the generic type parameters (if any):
LinkedList<int,string> list = new LinkedList<int,string>();
Type type = list.GetType();
MethodInfo methodInfo = type.GetMethod("AddHead");
object[] args = {1,"AAA"};
methodInfo.Invoke(list,args);
The System.Array type is extended with many generic static methods
它用从 1 到 20 的所有整数初始化一个数组。然后,代码通过一个匿名方法和 Action 委托,使用 Array.ForEach() 方法来跟踪这些数字。使用第二个匿名方法和 Predicate 委托,代码通过调用 Array.FindAll() 方法(它返回另一个相同的一般类型的数组),来查找该数组中的所有质数。最后,使用相同的 Action 委托和匿名方法来跟踪这些质数。
int[] numbers = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
Action<int> trace = delegate(int number)
{
Trace.WriteLine(number);
};
Predicate<int> isPrime = delegate(int number)
{
switch(number)
{
case 1:case 2:case 3:case 5:case 7:
case 11:case 13:case 17:case 19:
return true;
default:
return false;
}
};
Array.ForEach(numbers,trace);
int[] primes = Array.FindAll(numbers,isPrime);
Array.ForEach(primes,trace);
The data structures in System.Collections are all Object-based, and thus inheriting the two problems described at the beginning of this article, namely, inferior performance and lack of type safety. .NET 2.0 introduces a set of generic collections in the System.Collections.Generic namespace.
Mapping System.Collections.Generic to System.Collections
System.Collections.Generic |
System.Collections |
Comparer<T> |
Comparer |
Dictionary<K,T> |
HashTable |
LinkedList<T> |
- |
List<T> |
ArrayList |
Queue<T> |
Queue |
SortedDictionary<K,T> |
SortedList |
Stack<T> |
Stack |
ICollection<T> |
ICollection |
IComparable<T> |
System.IComparable |
IDictionary<K,T> |
IDictionary |
IEnumerable<T> |
IEnumerable |
IEnumerator<T> |
IEnumerator |
IList<T> |
IList |