C# 泛型入门,第二部分






4.95/5 (19投票s)
我们将继续探索泛型的基础知识。
文章索引
引言
在第一部分,我们介绍了类型参数。我们构建的示例泛型类并没有做很多事情。基本上它所做的就是存储值。现在让我们看看如何将一些逻辑构建到泛型类中。
编译器知道什么。
要理解泛型,你需要记住编译器知道什么,以及它不可能知道什么。
考虑以下泛型类
public class GenericDemo<T>
{
public void Foo(T Value)
{
//what does the compiler know about Value?
}
}
编译器只确定 Value 参数的一件事:它属于 Object 类型。任何创建此泛型类实例的人都可以将任何类型作为类型参数传递。所有可能性之间的唯一共同点是它们必须是 Object 类型。
这意味着在 Foo 方法内部,我们只能对 Value 参数做我们可以对对象做的事情。
在此处查看对象的文档
https://msdn.microsoft.com/en-us/library/system.object(v=vs.110).aspx
正如您所看到的,您可以对 Value 做的事情是有限的。
那么在这个例子中,我们如何为编译器提供更多关于 T 类型参数的信息呢?我们可以通过添加约束来告诉编译器更多关于类型参数的信息。
作为参考,这里是 .Net 类型参数约束的文档: https://msdn.microsoft.com/en-us/library/d5x73970.aspx
所以让我们添加一个约束。
public class GenericDemo<T> where T : IComparable<T>
{
public T GetBiggerValue(T value1, T value2)
{
if (value1.CompareTo(value2) >= 0) return value1;
return value2;
}
}
第一行的 "where T : iComparable<T>" 部分告诉编译器类型参数 T 必须实现 IComparable<T> 接口。由于 value1 和 value2 都属于 T 类型,现在可以使用 CompareTo 方法,而以前它不可用。
我们可以这样调用 GetBiggerValue 方法
var biggestString = GenericDemo<string>.GetBiggerValue("b", "a"); //"b":
var biggestInt = GenericDemo<int>.GetBiggerValue(123, 456); //456
我们允许传递 int 和 string 作为类型参数,因为它们都实现了 IComparable。
这无法编译
var doesNotWork = GenericDemo<char[]>.GetBiggerValue("a".ToCharArray(), "b".ToCharArray()); //DOES NOT COMPILE!
它无法编译的原因是,类型 char[] 没有实现 IComparable,并且我们添加的约束要求传递的所有类型参数都实现 IComparable。
其他约束类型
让我们快速看一下其他约束类型。
无参数构造函数约束
public class HasParameterlessConstructor
{
public HasParameterlessConstructor() { }
}
public class DoesNotHaveParameterlessConstructor
{
public DoesNotHaveParameterlessConstructor(int parameter1, string paramter2) { }
}
public class GenericDemo<T> where T : new()
{
public static T CreateNewInstance()
{
var newInstance = new T();
return newInstance;
}
}
上面的示例中的约束是 "where T : new()"。这告诉编译器类型 T 必须有一个无参数构造函数。在示例中,您还将看到两个名为 HasParameterlessConstructor 和 DoesNotHaveParameterlessConstructor 的类。
这里演示了这个约束的实际应用
var compiles = GenericDemo<HasParameterlessConstructor>.CreateNewInstance();
var alsoCompiles = GenericDemo<List<int>>.CreateNewInstance();
var doesNotCompile = GenericDemo<DoesNotHaveParameterlessConstructor>.CreateNewInstance();
前两行都编译,因为 HasParameterlessConstructor 和 List<int> 都有一个无参数构造函数。这意味着我们可以通过调用 "new T()" 来创建一个 T 的新实例,就像我们在 CreateNewInstance 方法内部做的那样。
第三行无法编译,因为 DoesNotHaveParameterlessConstructor 没有不带参数的构造函数。"new T()" 对于 DoesNotHaveParameterlessConstructor 来说是不可能的。您只能通过调用 new T(1,"a") 来创建 DoesNotHaveParameterlessConstructor 的实例,因为它的构造函数接受一个 int 和一个字符串,并且它没有其他构造函数。
引用类型和值类型约束
有关引用类型和值类型的更多信息,请参阅此文章。
https://msdn.microsoft.com/en-us/library/t63sy5hs.aspx?f=255&MSPPError=-2147217396
以下示例中的 'class' 约束告诉编译器 T 必须是 引用 类型。
public class GenericDemo<T> where T : class, IComparable<T> //T must be a reference type
{
public static T GetBiggerValue(T value1, T value2)
{
if (value1.CompareTo(value2) >= 0) return value1;
return value2;
}
}
以下示例中的 'struct' 约束告诉编译器 T 必须是 值 类型。
public class GenericDemo<T> where T : struct, IComparable<T> //T must be a value type
{
public static T GetBiggerValue(T value1, T value2)
{
if (value1.CompareTo(value2) >= 0) return value1;
return value2;
}
}
基本示例
让我们构建一个使用约束的泛型类的基本示例,它有一些实际用途。
此示例的作用是允许您将作业添加到集合中,然后它们将按优先级顺序执行。
public interface IJob
{
int GetPriority();
void Execute();
}
public class JobRunner<T> where T : IJob, IComparable<T>
{
private List<T> _jobList = new List<T>();
private class JobComparer : IComparer<T>
{
public int Compare(T x, T y)
{
return x.CompareTo(y);
}
}
public void AddJob(T job)
{
_jobList.Add(job);
}
public void ExecuteJobs()
{
//order jobs according to priority. Note that the JobComparer is not neccesary,
//this is only to demonstrate how the IComparer constraint adds information the
//compiler can use.
var sortedByPriority = _jobList.OrderByDescending(x => x, new JobComparer());
foreach (var job in sortedByPriority)
{
job.Execute();
}
}
}
public class WriteToConsoleJob : IJob, IComparable<IJob>
{
private int _priority;
private string _toWrite;
public WriteToConsoleJob(int priority, string toWrite)
{
_toWrite = toWrite;
_priority = priority;
}
public int GetPriority()
{
return _priority;
}
public void Execute()
{
Console.WriteLine(_toWrite);
}
public int CompareTo(IJob other)
{
return this.GetPriority().CompareTo(other.GetPriority());
}
}
这个泛型类可以这样使用
var jobRunner = new JobRunner<WriteToConsoleJob>();
var highPriorityJob = new WriteToConsoleJob(1000, "high priority job");
var lowPriorityJob = new WriteToConsoleJob(50, "low priority job");
var mediumPriorityJob = new WriteToConsoleJob(500, "medium priority job");
jobRunner.AddJob(lowPriorityJob);
jobRunner.AddJob(highPriorityJob);
jobRunner.AddJob(mediumPriorityJob);
jobRunner.ExecuteJobs();
控制台上的输出将是
high priority job
medium priority job
low priority job
这一行
public class JobRunner<T> where T : IJob, IComparable<T>
告诉编译器 T 必须同时实现 IJob 和 IComparable。IJob 允许在 T 类型的实例上调用 GetPriority() 和 Execute()。IComparable 允许调用 CompareTo。因此,您可以创建自己的作业,只要它同时实现了 IJob 和 IComparable,就可以做任何您想做的事情。
结论
现在我们了解了类型参数约束的作用。您现在拥有的知识应该允许您构建自己的泛型类。它还应该允许您理解您遇到的使用泛型的大部分 C# 代码。