65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 泛型入门,第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (19投票s)

2015 年 11 月 25 日

CPOL

3分钟阅读

viewsIcon

23150

我们将继续探索泛型的基础知识。

文章索引

C# 泛型入门 - 第一部分

C# 泛型入门 - 第 2

引言

在第一部分,我们介绍了类型参数。我们构建的示例泛型类并没有做很多事情。基本上它所做的就是存储值。现在让我们看看如何将一些逻辑构建到泛型类中。

编译器知道什么。

要理解泛型,你需要记住编译器知道什么,以及它不可能知道什么。 

考虑以下泛型类

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# 代码。

 

 

© . All rights reserved.