zoukankan      html  css  js  c++  java
  • 泛型和反射

    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

  • 相关阅读:
    python返回列表最大值(java返回数组最大值)
    Mysql的5种索引添加类型
    阿里云中quick bi用地图分析数据时维度需转换为地理区域类型
    根据变量查找元素,并修改数值的实践
    Linux 通过命令设置网络
    mysql 实现 上一行减去下一行
    Spark 安装与启动
    Kafka 入门之集群部署遇到问题
    rmp使用方法
    Mysql 导入数据的一种方法
  • 原文地址:https://www.cnblogs.com/rush/p/2709113.html
Copyright © 2011-2022 走看看