C# in depth ( 第三章 用泛型实现参数化类型)

3.1 为什么需要泛型

  • 避免了强制转换,使代码更易读易写,也就减少了出bug的几率。
  • 提升了性能
  1. 由于编译时做了更多的检查,运行时的检查就可以少做很多。
  2. JIT能够聪明地处理值类型,能消除很多情况下的装箱和拆箱处理。

3.2 日常使用的简单泛型

3.2.1通过例子来学习: 泛型字典

 class DictionaryDemo
    {
        static Dictionary<string,int> CountWords(string text)
        {
            Dictionary<string,int> frequencies;
            frequencies = new Dictionary<string,int>();
                
            string[] words = Regex.Split(text, @"\W+");
                
            foreach (string word in words)
            {
                if (frequencies.ContainsKey(word))
                {
                    frequencies[word]++;
                }
                else
                {
                    frequencies[word] = 1;
                }
            }
            return frequencies;
        }

        static void Main()
        {
            string text = @"Do you like green eggs and ham?
                            I do not like them, Sam-I-am.
                            I do not like green eggs and ham.";

            Dictionary<string, int> frequencies = CountWords(text);
            foreach (KeyValuePair<string, int> entry in frequencies)
            {
                string word = entry.Key;
                int frequency = entry.Value;
                Console.WriteLine("{0}: {1}", word, frequency);
            }
        }
    }

3.2.2 泛型类型和类型参数

  • 泛型有两种形式
  1. 泛型类型(类,接口,委托和结构)
  2. 泛型方法
  • 类型参数  类型参数是真实类型的占位符 在Dictionary<TKey,TValue>中类型参数是TKey和TValue。
  • 类型实参 使用泛型类型或方法时,要用真实的类型替代,这些真实的类型成为类型实参(type argument)在代码清单中类型实参是string(代替TKey)和int(代替TValue)
  • 未邦定泛型类型(unbound generic type) 如果没有为泛型类型参数提供类型实参,那么这就是一个未邦定泛型类型(unbound generic type)
  1. 在C#代码中唯一能看见未邦定代码的地方就是typeof 
        var type=   typeof(Dictionary<,>);
  • 如果指定了类型实参,该类型就称为一个已构造的类型(constructed type), 已构造类型又可以是开放或封闭的。
  1. 开放类型(open type)还包含一个类型参数
  2. 封闭类型(closed type)则不是开放的,类型的每个部分都是明确的。
  • 未绑定泛型类型相当于已构造类型的蓝图。已构造类型又是实际对相的蓝图,这一点和非泛型类型的作用是相似的
非泛型蓝图 泛型蓝图
  Dictionary<TKey,TValue>(未绑定泛型类型)
  指定类型参数 指定类型参数
  Dictionary<string,int>(已构造类型) Dictionary<byte,long>(已构造类型)
 实例化 实例化 实例化
Hashtable实例 Dictionary<string,int>实例 Dictionary<byte,long>实例

 

泛型类型中的方法签名 类型参数被替换之后的方法签名
void Add (TKey key,Tvalue value) void Add (string key,int value)

注意上表中的方法并不是泛型方法,只是泛型类型中的普通方法,只是凑巧使用了作为类型一部分声明的类型参数。

 

  • 泛型类型可以重载MyType,MyType<T>,MyType<T,U>,MyType<T,U,V> 所有这些定义可以被放到同一个命名空间,类型参数的名称并不重要,重要的是个数,泛形方法也一样。

 3.2.3 泛型方法和判读泛型声明

 

 class ListConvertAll
    {
        static double TakeSquareRoot(int x)
        {
            return Math.Sqrt(x);
        }

        static void Main()
        {
            List<int> integers = new List<int>();
            integers.Add(1);
            integers.Add(2);
            integers.Add(3);
            integers.Add(4);

            Converter<int, double> converter = TakeSquareRoot;
            List<double> doubles = integers.ConvertAll<double>(converter);

            foreach (double d in doubles)
            {
                Console.WriteLine(d);
            }
        }
    }
class GenericMethodDemo
    {
        static List<T> MakeList<T>(T first, T second)
        {
            List<T> list = new List<T>();
            list.Add(first);
            list.Add(second);
            return list;
        }

        static void Main()
        {
            List<string> list = MakeList<string>("Line 1", "Line 2");
            foreach (string x in list)
            {
                Console.WriteLine(x);
            }
        }
    }

3.3深化与提高

 3.3.1类型约束

  •  引用类型的约束
    struct RefSample<T> where T: class
    
    RefSample<string>仍然是值类型,以这种方式约束了一个参数类型后,可以使用==来比较引用(包括null)
    
    需要注意的是除非还存在其他约束,否则只能比较引用。
  •  值类型的约束
    class ValSample<T> where T:struct
    可以确保使用的类实参是值类型,包括枚举(enums)。但是它将可空类型排除在外。
    类型参数被约束为值类型后就不允许使用==和!=进行比较。
  • 构造函数类型约束

    T :new() 必须是所有类型参数的最后一个约束,它检查类型实参是否有一个可用于创建类型实例的无参构造函数。这适用于所有值类型,所有没有显示声明构造函数的非静态,非抽象类;所有显式声明了一个公共无参构造函数的非抽象类。
    
    public T CreateInstance<T>() where T: new()
    {
        return new T();
    }
    
    CreateInstance<int>() 和 CreateInstance<object>()都是有效的,
    但是CreateInstance<string>()是无效的,因为string没有无参构造函数
  • 转换类型约束
    可以指定多个接口,但只能指定一个类。例如,以下声明毫无问题(尽管很难满足):
    Class Sample<T> where T: Stream, IEnumerable<string>, IComparable<int>
    
    但以下声明就有问题了:
    Class Sample<T> where T: Stream, ArrayList, IComparable<int>

    总之,任何类型都不能派生自多个类,对于这样一个约束要么是永远无法满足(如上),要么它的一部分是多余的(例如,规定类型必须从Stream和MemoryStream派生)
    此外还有一系列的限制:指定的类不可以是结构,密封类,或者以下任何特殊类型:
    System.Object
    System.Enum
    System.ValueType
    System.Delegate
  • 组合约束

    有效
    class sample<T> where T: class, IDisposable, new()
    class sample<T> where T: struct, IDisposable
    class sample<T,U> where T :class where U :struct, T  (T是object,或者是U实现的一个接口)
    class sample<T,U> where T: Stream where U : IDisposable
    
    无效
    class sample<T> where T: class, struct
    class Sample<T> where T: Stream, class
    class Sample<T> where T : new(), Stream (new 要放在最后)
    class Sample<T> where T : IDisposable, Stream
    class Sample<T> where T : XmlReader, IComparable, IComparable
    class Sample<T,U> where T: struct, where U:class, T (和上方有效的第三条不一样,相反)
    class Sample<T,U> where T: Stream, U:IDisposable (没有where)

     

  • 约束可以分为 主要约束,次要约束,和构造函数约束。
  1. 主要约束可以为引用类型约束,值类型约束或使用类的转换类型约束。    
  • 主要约束是可选的,但是只能有一个
  1. 次要约束为使用接口或其他类型参数的转换类型约束。                        
  • 次要约束则可以有多个
  1. 构造函数约束                                                                        
  •  构造函数也是可选的(如果拥有了值类型约束,就不能再使用构造函数约束)

 3.3.2 泛型方法类型实参的类型推断 (类型推断只适用于泛型方法,不适用于泛型类型)

 3.3.3 实现泛型

1.默认值表达式

 class DefaultValueComparison
    {
        static int CompareToDefault<T>(T value)
            where T : IComparable<T>
        {
            return value.CompareTo(default(T));
        }
    
        static void Main()
        {
            Console.WriteLine(CompareToDefault("x"));  //1
            Console.WriteLine(CompareToDefault(10));   //1
            Console.WriteLine(CompareToDefault(0));    //0
            Console.WriteLine(CompareToDefault(-10));  //-1
            Console.WriteLine(CompareToDefault(DateTime.MinValue));  //0
        }
    }

 2.直接比较

  • 如果一个类型参数是未约束的(即没有对其应用约束),那么且只能在将该类型的值与null进行比较时才能使用==和!=操作符。不能直接比较两个类型的值(因为值类型不知道如何比较)
  • 如果类型参实参是一个引用类型,会进行正常的引用比较。
  • 如果为T提供的类型实参是一个非可空值类型,与null进行比较的结果总是显示他们不相等(这样一来JIT编译器就可以移除这个比较)。如果类型参数是可空值类型,那么就会自然而然与类型的空值进行比较。
  • 如果它只是一个引用类型,那么执行的是简单的引用比较。如果它被进一步约束成继承自某个重载了==和!=操作符的特定类型,就会使用重载的操作符。但要注意,假如调用者指定的类型实参恰巧也进行了重载,那么这个重载操作符是不会使用的。

 

 class OperatorOverloading
    {
        static bool AreReferencesEqual<T>(T first, T second)
            where T : class
        {
            return first == second;  (此处不会调用string的重载)
        }

        static void Main()
        {
            string name = "Jon";
            string intro1 = "My name is " + name;
            string intro2 = "My name is " + name;
            Console.WriteLine(intro1 == intro2);  //true
            Console.WriteLine(AreReferencesEqual(intro1, intro2));//false
        }
    }
  •  并非只有操作符才有这个问题,遇到泛型类型时,编译器会在编译未绑定的泛型类型时,就解析好所有方法重载,而不是等到执行时
Console.WriteLine(default(T));这个语句总是被解析成调用Console.WriteLine(object value)。
即使为T传递的类型实参恰好是string,也不会调用Console.WriteLine(string value)
  • 泛型接口比较
  1. IComparaer<T>和IComparable<T>用于排序,而IEqualityComparer<T>和IEquatable<T>通过某种标准来比较两个项的相等性,或查找出某个项的散列(通过与相等性概念匹配的方式)。
  2. IComparaer<T>和IEqualityComparer<T>的实例能够比较两个不同的值,而IComparable<T>和IEquatable<T>的实例则可以比较它的本身和其他值。

 3.完整的比较例子:表示一对值

 [Description("Listing 3.06")]
    public sealed class Pair<TFirst, TSecond> : IEquatable<Pair<TFirst, TSecond>>
    {
        private static readonly IEqualityComparer<TFirst> FirstComparer =
           EqualityComparer<TFirst>.Default;
        private static readonly IEqualityComparer<TSecond> SecondComparer =
           EqualityComparer<TSecond>.Default;

        private readonly TFirst first;
        private readonly TSecond second;

        public Pair(TFirst first, TSecond second)
        {
            this.first = first;
            this.second = second;
        }

        public TFirst First { get { return first; } }

        public TSecond Second { get { return second; } }

        public bool Equals(Pair<TFirst, TSecond> other)
        {
            return other != null &&
               FirstComparer.Equals(this.First, other.First) &&
               SecondComparer.Equals(this.Second, other.Second);
        }

        public override bool Equals(object o)
        {
            return Equals(o as Pair<TFirst, TSecond>);
        }

        public override int GetHashCode()
        {
            return FirstComparer.GetHashCode(first) * 37 +
               SecondComparer.GetHashCode(second);
        }
    }

 
  •  使用包含泛型方法的非泛型类型进行类型推断
    [Description("Listing 3.07")]
        public static class Pair
        {
            public static Pair<TFirst, TSecond> Of<TFirst, TSecond>(TFirst first, TSecond second)
            {
                return new Pair<TFirst, TSecond>(first, second);
            }
        }

     Pair<int,string> pair = Pair.Of(10,"value");

 3.4高级泛型

3.4.1静态字段和静态构造函数

  • 每个封闭类型都有自己的静态字段集
 class StaticFieldPerClosedType
    {
        class TypeWithField<T>
        {
            public static string field;

            public static void PrintField()
            {
                Console.WriteLine(field + ": " + typeof(T).Name);
            }
        }

        static void Main()
        {
            TypeWithField<int>.field = "First";
            TypeWithField<string>.field = "Second";
            TypeWithField<DateTime>.field = "Third";

            TypeWithField<int>.PrintField();
            TypeWithField<string>.PrintField();
            TypeWithField<DateTime>.PrintField();
        }
    }
  •  静态初始化程序(static initializer)和静态构造函数也一样(static constructor)

 

class StaticConstructors
    {
        class Outer<T>
        {
            public class Inner<U, V>
            {
                static Inner()
                {
                    Console.WriteLine("Outer<{0}>.Inner<{1},{2}>",
                                      typeof(T).Name,
                                      typeof(U).Name,
                                      typeof(V).Name);
                }

                public static void DummyMethod()
                {
                }
            }
        }

        static void Main()
        {
            Outer<int>.Inner<string, DateTime>.DummyMethod();
            Outer<string>.Inner<int, int>.DummyMethod();
            Outer<object>.Inner<string, object>.DummyMethod();
            Outer<string>.Inner<string, object>.DummyMethod();
            Outer<object>.Inner<object, string>.DummyMethod();
            Outer<string>.Inner<int, int>.DummyMethod();
        }
    }

3.4.2 JIT编译器如何处理泛型

  1. JIT为每个以值类型作为类型实参的封闭类型都创建不同的代码。
  2. 然而所有使用引用类型(string, Stream,StringBuilder等)作为类型实参的封闭类型都共享相同的本地代码。
  • 之所以能这么做,是由于所有引用都具有相同的大小(32位CLR上是4字节,64位CLR上是8字节)无论实际引用的是什么,引用数组的大小是不会发生变化的。

3.4.3 泛型迭代

  class CountingEnumerableExample
    {
        class CountingEnumerable : IEnumerable<int>
        {
            public IEnumerator<int> GetEnumerator()
            {
                return new CountingEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }

        class CountingEnumerator : IEnumerator<int>
        {
            int current = -1;

            public bool MoveNext()
            {
                current++;
                return current < 10;
            }

            public int Current
            {
                get { return current; }
            }

            object IEnumerator.Current
            {
                get { return Current; }
            }

            public void Reset()
            {
                throw new NotSupportedException();
            }

            public void Dispose()
            {
            }
        }

        static void Main()
        {
            CountingEnumerable counter = new CountingEnumerable();
            foreach (int x in counter)
            {
                Console.WriteLine(x);
            }
        }
    }

 3.4.4 反射和泛型

  1. 对泛型类型使用typeof  
  • 反射的一切都是围绕“检查对象及其类型”展开的。所以,最重要的就是获取System.Type对象的引用。这样就可以访问与特定类型有关的所有信息。
     class TypeofOperator
        {
            static internal void DemonstrateTypeof<X>()
            {
          Console.WriteLine(typeof(X));  //System.Int32
          Console.WriteLine(typeof(List<>)); //System.Collections.Generic.List`1[T]
          Console.WriteLine(typeof(Dictionary<,>));//System.Collections.Dictionary'2[System.String,System.Int32]
          Console.WriteLine(typeof(List<X>)); Console.WriteLine(typeof(Dictionary<string, X>));
    Console.WriteLine(
    typeof(List<long>));
    Console.WriteLine(
    typeof(Dictionary<long, Guid>)); }

    static void Main()
    {
    DemonstrateTypeof
    <int>();
    }
    }



     2.System.Type的属性和方法

   [Description("Listing 3.12")]
    class GenericTypeReflection
    {
        static void Main()
        {
            string listTypeName = "System.Collections.Generic.List`1";

            Type defByName = Type.GetType(listTypeName);

            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);

            Type defByTypeof = typeof(List<>);
            Type defByMethod = closedByName.GetGenericTypeDefinition();

            Console.WriteLine(defByMethod == defByName);
            Console.WriteLine(defByName == defByTypeof);
        }
    }
无论怎样获取对一个特定类型对象的引用,都只涉及一个这样的对象

  3.反射泛型方法

 class GenericMethodReflection
    {
        public static void PrintTypeParameter<T>()
        {
            Console.WriteLine (typeof(T));
        }

        static void Main()
        {
            Type type = typeof(GenericMethodReflection);
            MethodInfo definition = type.GetMethod("PrintTypeParameter");        
            MethodInfo constructed;
            constructed = definition.MakeGenericMethod(typeof(string));       
            constructed.Invoke(null, null);
        }
    }

3.5 泛型在C#和其他语言中的限制

 3.5.1泛型可变性的缺乏

1.泛型为何不支持协变性  静态类型的全部意义在于在代码运行之前找出错误

     Animal[] animals = new Cat[5];//编译可过
     animals[0] = new Dog();//运行时报错

     List<Animal> lstanimals = new List<Cat>(); //编译不可过
     lstanimals.Add(new Dog());

 

 2.协变性在什么时候有用

 3.逆变在什么时候有用

3.5.2 缺乏操作符约束或者"数值"约束

3.5.3 缺乏泛型属性,索引器和其他成员类型。

3.5.4 同C++模板的对比

  • C++只编译一次,一个C++程序以10种不同方式使用一个标准模板,就会在程序中包含代码10次。但是在C#中,一个类似程序如果以10种不同的方式来使用自框架的一个泛型类型,那么根本不会包含泛型类型的代码。相反,它只是引用一下泛型类型。执行时需要多少个不同的版本,JIT就会编译多少个。

3.5.5 和Java泛型的对比

 

 

posted @ 2015-07-20 15:55  莱茵哈特  阅读(270)  评论(0编辑  收藏  举报