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

  • 相关阅读:
    convert image to base64 and post to RESTful wcf
    在android webview实现截屏的手动tounchmove裁剪图片
    How to use jquery ajax and android request security RESTful WCF
    using swfUpload in asp.net mvc
    using HttpClient and sending json data to RESTful server in adroind
    ODP.NET数据访问
    android image watermark
    解决国内不能访问github的问题
    idapro权威指南第二版阅读笔记第九章 交叉引用和绘图功能
    idapro权威指南第二版阅读笔记第二章 逆向和反汇编工具
  • 原文地址:https://www.cnblogs.com/rush/p/2709113.html
Copyright © 2011-2022 走看看