Fork me on GitHub

泛型和反射

1.1.1 摘要

在前一博文《.NET 中的泛型 101》中我们介绍了泛型的基本用法,现在我们继续介绍泛型的进阶用法(如:泛型的比较接口、迭代实现、泛型类型和方法的反射)。

泛型的比较接口提供了实现对象比较和排序。

由于公共语言运行库 (CLR) 能够在运行时(Run time)访问泛型类型信息,所以可以使用反射获取关于泛型类型的信息,方法与用于非泛型类型的方法相同。

在.NET Framework 1.0中,我们可以使用Type.GetType()获取Type类型的对象,当然1.0时,还没有引入泛型。

在.NET Framework 2.0,Type类增添了几个新成员以获取泛型类型的运行时(Run time)信息。

本文目录

1.1.2 正文

泛型比较接口

泛型的四种比较接口:IComparer<T>、IComparable<T>、IEqualityComparer<T>和IEquatable<T>。

其中,IComparer<T>和IComparable<T>实现比较排序,而IEqualityComparer<T>和IEquatable<T>实现条件比较并且获取相应元素的哈希值。

IComparer<T>和IEqualityComparer<T>可以实现不同类型之间的比较,而IComparable<T>和IEquatable<T>只能在同一类型中进行比较。

假设,我们定义了一个学生类,它用来记录学生的基本信息(如:Id、FirstName、LastName和Tel等),具体定义如下:

/// <summary>
/// The student model.
/// </summary>
public class Student
{
    public long Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public long Tel { get; set; }
}

现在,我们创建一个Student类型的List。

// Creates student object with initializer.
var students = new List<Student>()
    {
        new Student() { Id = 245712348, FirstName = "Ann", LastName = "Chen", Tel = "18022007281" },
        new Student() { Id = 245712345, FirstName = "Ada", LastName = "Cao", Tel = "18022007281" },
        new Student() { Id = 245712347, FirstName = "Rush", LastName = "Huang", Tel = "18022007281" },
        new Student() { Id = 245712346, FirstName = "Jackson", LastName = "Huang", Tel = "18022007281" },
        new Student() { Id = 245712349, FirstName = "Maggie", LastName = "Yip", Tel = "18022007281" },
    };

上面,我们在List中创建了五个学生对象,如果我们要根据学号(Id)对学生对象进行排序,这时可以通过实现IComparable<T>接口实现对象排序。

/// <summary>
/// The student model.
/// </summary>
public class Student : IComparable<Student>
{
    public long Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Tel { get; set; }

    #region Implementation of IComparable<Student>

    public int CompareTo(Student other)
    {
        if (this.Id > other.Id)
        {
            return 1;
        }
    }

    #endregion
}

我们通过实现IComparable<T>接口,让List根据Id进行排序,我们需要实现CompareTo()方法根据Id排序。

现在,我们又有一个疑问:如果Student类不仅仅根据Id排序,还希望根据LastName或FirstName排序,这时,我们可以给CompareTo()方法增加LastName或FirstName排序条件就OK了。

/// <summary>
/// The student model.
/// </summary>
public class Student : IComparable<Student>
{
    public long Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Tel { get; set; }

    #region Implementation of IComparable<Student>

    public int CompareTo(Student other)
    {
        if (this.Id > other.Id)
        {
            return 1;
        }

        // If the id is the same, then comparing lastname and first name.
        return string.Compare(
            this.LastName, other.LastName) != 0 ?
            string.Compare(this.LastName, other.LastName) : string.Compare(this.FirstName, other.FirstName);
    }

    #endregion
}

假设,需求变得更加糟糕排序条件不固定,有可能根据Id排序,也有可能根据LastName或FirstName排序。

这时,我们可以通过自定义类并且实现IComparer<T>接口,让List根据自定义排序条件排序,而且需要实现compare()方法;它包含两个对象的参数分别是x和y,如果x小于y返回一个负值,相等返回零,x大于y返回一个正数。

接下来,让我们定义排序条件根据学生的姓名进行排序,具体实现如下:

/// <summary>
/// The student comparer.
/// </summary>
public class StudnetComparer : IComparer<Student>
{
    #region Implementation of IComparer<Student>

    // Compares the lastname of students.
    public int Compare(Student x, Student y)
    {
        return string.Compare(x.LastName, y.LastName);
    }

    #endregion
}

上面,我们定义排序类StudnetComparer,它实现了IComparer<T>接口并且添加了排序条件,让StudnetComparer根据学生的姓名进行排序。

假设我们想根据Id排序,那么我们可以扩展StudnetComparer的Compare()方法,当让我们也可以定义一个新的比较类。

generic0

图1排序结果

泛型迭代

迭代是集合中最常用的操作之一,通过迭代可以访问集合中的元素,相信大家都使用foreach语句来访问集合中的元素,它就是通过迭代的方式去访问集合中的元素。

在C# 1.0中,迭代访问集合需要实现System.Collections.IEnumerable接口或有一个GetEnumerator()方法返回一个合适类型对象,通过该对象的MoveNext()方法和Current属性访问集合中的元素。

接下来,我们通过foreach语句迭代访问ArrayList集合中的元素,对于大家来说这再简单不过了,示例如下:

foreach (var number in numberArray)
{
    Console.WriteLine(string.Format("number: {0}", number));
}

我们知道只有类型实现了IEnumerable接口或有一个GetEnumerator()方法才可以通过foreach语句迭代访问,现在我们就有一个疑问foreach语句具体进行了哪些操作呢?

其实,foreach语句简化了整个迭代访问的过程,接下来我们将给出foreach语句的具体实现。

ArrayList numberArray = new ArrayList() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

IEnumerator enumerator = numberArray.GetEnumerator();
while (enumerator.MoveNext())
{
    // Inboxing operation.
    Object number = enumerator.Current;
    Console.WriteLine(string.Format("number: {0}", number));
}

首先,numberArray调用GetEnumerator()方法获取一个IEnumerator对象,接着遍历访问enumerator中的元素,这里我们要注意enumerator.Current获取的是ArrayList中的元素,由于集合中的元素是值类型,所以转换为引用类型(Object)需要进行装箱操作。

clip_image002

图2排序结果

在C# 2.0中,由于引入了泛型并且使用泛型接口IEnumerable<T>扩展了IEnumerable接口,那么foreach语句将可以使用IEnumerable或IEnumerable<T>接口访问集合中的元素。

generic1

图3 IEnumerable<T>接口

在前面给出的例子中,如果访问值类型集合时,那么值类型元素需要进行装箱操作;当访问泛型集合时(如:List<int>),那么值类型元素是否需要装箱操作呢?

IEnumerator<int> enumerator = numberArray.GetEnumerator();
while (enumerator.MoveNext())
{
    int number = enumerator.Current;
    Console.WriteLine(string.Format("number: {0}", number));
    ////string.Format(
    ////"Id:{0} FirstName:{1} LastName:{2} Tel:{3}",
    ////student.Id, student.FirstName, student.LastName, student.Tel));
}

var disposable = enumerator as IDisposable;
disposable.Dispose();

上面,我们给出了值类型泛型集合的访问实现,我们发现值类型元素无需转换为引用类型,所以无需进行装箱操作。

接下来,我们将通过foreach语句迭代访问引用类型集合studnets中的元素,具体实现如下:

foreach (var student in students)
{
    Console.WriteLine(
        string.Format(
        "Id:{0} FirstName:{1} LastName:{2} Tel:{3}",
        student.Id, student.FirstName, student.LastName, student.Tel));
}

其实,foreach语句背后的操作,首先,调用了students集合的GetEnumerator()方法,然后,通过MoveNext()方法遍历集合中的元素,接着通过Current属性获取当前元素对象。

IEnumerator enumerator = students.GetEnumerator();
while (enumerator.MoveNext())
{
    Student student = enumerator.Current as Student;
    Console.WriteLine(
        string.Format(
        "Id:{0} FirstName:{1} LastName:{2} Tel:{3}",
        student.Id, student.FirstName, student.LastName, student.Tel));
}

var disposable = enumerator as IDisposable;
disposable.Dispose();

泛型类型的反射

反射提供了封装程序集、模块和类型的对象(Type 类型),我们可以通过反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了属性,可以利用反射对它们进行访问。

C#使用typeof()方法来获的编译时类型。

在泛型中,typeof()方法有两种用法,一种用来获取“开放”泛型(Generic type)的Type对象,另一种是获取“封闭”泛型即构造泛型(Constructed type)的Type对象。

现在,我们有一个疑问什么是“开放”泛型,什么是“封闭”泛型呢?

其实,理解所有这一切的关键是理解两种不同的Type对象和泛型类之间的关系,假设我们定义了一个泛型类,然后,给它添加一个或多个类型不确定的成员,直到真正调用它的时候类型才确定下来,这就是所谓的“开放”泛型;当我们声明了一个“开放”泛型的引用并且提供具体的成员类型,这就是所谓的“封闭”泛型即构造泛型。

接下来,我们将介绍通过typeof()方法获取公开和封闭泛型的Type对象。

Console.WriteLine(typeof(string));
// Open type.
Console.WriteLine(typeof(List<>));
// If have mutiple parameter, should keep commas.
Console.WriteLine(typeof(Dictionary<,>));

// Constructed type.
Console.WriteLine(typeof(List<string>));
Console.WriteLine(typeof(Dictionary<string, long>));
Console.WriteLine(typeof(List<long>));
Console.WriteLine(typeof(Dictionary<long, Guid>));
generic3

图4 typeof方法获取Type对象

上面,我们通过typeof()方法获取“开放”和“封闭”泛型的Type对象。

其中有两点是我们要注意的,首先输出结果中包含的数字如:‘1或‘2表示参数个数,而且,我们发现对于“开放”泛型参数类型都是不确定,它们使用了如:T或TValue占位符,而“封闭”泛型参数类型都确定的。

前面,我们使用typeof()方法获取泛型类型的Type对象,其实,我们还可以使用Type类的成员方法获取泛型类型的Type对象,它们分别是 GetGenericTypeDefinition()和MakeGenericType()。

GetGenericTypeDefinition()方法获取一个表示可用于构造当前泛型类型的泛型类型定义的 Type对象,MakeGenericType()方法替代由当前泛型类型定义的类型参数组成的类型数组的元素,并返回表示结果构造类型的 Type 对象。

这两个方法的描述十分绕口,简而言之,我们通过GetGenericTypeDefinition()方法获取“封闭”泛型的泛型定义,而MakeGenericType()方法根据泛型定义获取“封闭”类型。

其实,在C# 1.0中也包含类似功能的方法Type.GetType()和Assembly.GetType()方法。

接下来,我们将使用Type的成员方法获取泛型类型的Type对象。

string listTypeName = "System.Collections.Generic.List`1";

Type defByName = Type.GetType(listTypeName);

// Retrieves the type of List<string> through the approach as below.
Type closedByName = Type.GetType(listTypeName + "[System.String]");
Type closedByMethod = defByName.MakeGenericType(typeof(string));
Type closedByTypeof = typeof(List<string>);

Console.WriteLine(closedByMethod == closedByName);
Console.WriteLine(closedByName == closedByTypeof);

// Retrieves open type object through the approach as below.
Type defByTypeof = typeof(List<>);
Type defByMethod = closedByName.GetGenericTypeDefinition();

Console.WriteLine(defByMethod == defByName);
Console.WriteLine(defByName == defByTypeof);

Console.ReadKey();

// OUTPUT:
// True
// True
// True
// True

上面的输出结果都为True,但我们注意到defByMethod、defByName、defByName和defByTypeof都是特定类型Type对象,而我们使用了“==”判断两个Type对象是否相同,也就是说,对于同一泛型类型分别通过typeof()或GetType()方法得到的是同一个Type对象引用。

泛型方法的反射

前面,我们介绍了使用typeof()方法或Type的成员方法获取泛型类型的Type对象,至于泛型方法的反射,我们使用的是MethodInfo类的成员方法MakeGenericMethod()。

/// <summary>
/// Defines a generic class.
/// </summary>
/// <typeparam name="T">T can be value or reference type.</typeparam>
public class GenericClass<T>
{
    private T _t = default(T);

    public GenericClass(T t)
    {
        _t = t;
        Console.WriteLine("GenericClass<{0}>( {1} ) object created",
                typeof(T).FullName, _t.ToString());
    }
    
    public T GetValue()
    {
        Console.WriteLine("GetValue() method invoked, returning {0}", _t.ToString());
        return _t;
    }

    public static U StaticGetValue<U>(U u)
    {
        Console.WriteLine("StaticGetValue<{0}>( {1} ) method invoked",
                typeof(U).FullName, u.ToString());
        return u;
    }
}

上面,我们定义了泛型类GenericClass<T>,接下来,我们将使用MakeGenericMethod()方法获取泛型方法的MethodInfo对象,接着通过Invoke()方法调用该泛型方法。

// Invokes the static template method directly
GenericClass<int>.StaticGetValue(23);

// Gets the open generic method type
// Notes, we should specify the class type T first.
MethodInfo openGenericMethod = typeof(GenericClass<string>).GetMethod("StaticGetValue");

// Gets the close generic method type, by supplying the generic parameter type
MethodInfo closedGenericMethod = openGenericMethod.MakeGenericMethod(typeof(int));

object o2 = closedGenericMethod.Invoke(null, new object[] { 20120929 });

Console.WriteLine("o2 = {0}", o2.ToString());

generic4

图5 泛型方法的反射

首先,我们使用GetMethod()方法获取开放的泛型方法,接着使用MakeGenericMethod()构造封闭的泛型方法,最后调用MethodInfo对象的Invoke()方法传递参数和调用泛型方法StaticGetValue<U>()。

1.1.3 总结

本文介绍泛型的进阶使用方法,如:泛型接口、迭代的实现、泛型类型的反射和泛型方法的反射。

泛型接口实现对象比较和排序,如:实现IComparer<T>、IComparable<T>、IEqualityComparer<T>和IEquatable<T>接口。

对于迭代访问集合中的元素,我们一般可以使用foreach语句实现,这里我们介绍了foreach语句的迭代访问实现。

最后,通过介绍“开放”泛型类型、“封闭”泛型类型、“开放”泛型方法和“封闭”泛型方法;我们学会了如何根据Type对象获取相应的泛型对象或方法,我们学习了如何使用MakeGenericType()方法构造“封闭”泛型类型,这样我们就可以通过Activator.CreateInstance()方法实例化该类型;通过MakeGenericMethod()方法构造封闭”泛型方法,然后调用MethodInfo对象的Invoke()方法传递参数和调用泛型方法。

祝大家中秋和国庆节快乐,身体健康。

参考

[1] http://en.csharp-online.net/Generic_types

[2] http://www.codeproject.com/Articles/22088/Reflecting-on-Generics

[3] http://www.amazon.cn/C-in-Depth-Skeet-Jon/dp/1935182471/ref=sr_1_2?ie=UTF8&qid=1348972408&sr=8-2

posted @ 2012-09-30 10:34  JK_Rush  阅读(15571)  评论(17编辑  收藏  举报