C# のジェネリックス

WBOY
リリース: 2016-07-06 13:30:22
オリジナル
1448 人が閲覧しました

この記事では主に C# のジェネリックスについて説明します。ジェネリックスは C# で非常に重要な役割を果たし、可読性とパフォーマンスの高いコードを作成する上で重要な役割を果たすためです。私のチームのコードに多数の非ジェネリック コレクションと暗黙的なボックス化およびボックス化解除操作が含まれているのを何度も見たとき、私はジェネリック ベースを追加することを提案します

この記事では主に C# のジェネリックスについて説明します。ジェネリックスは C# で非常に重要な役割を果たし、可読性とパフォーマンスの高いコードを作成する上で重要な役割を果たすためです。私のチームのコードに多数の非ジェネリック コレクションと暗黙的なボックス化およびボックス化解除操作が含まれているのを何度も目にしたとき、私はジェネリックの基本を改善することを提案します。

1. ジェネリック医薬品とは

    • ジェネリックは、C# 2 の非常に重要な新機能です。これにより、コードの可読性が向上し、多数のセキュリティ チェックが実行フェーズからコンパイル フェーズに転送されるため、コードのセキュリティとパフォーマンスが向上します。基本的に、ジェネリックにより型とメソッドのパラメータ化が可能になります。

2. ジェネリックを使用する理由とジェネリックが解決する問題

まず次のコードを見てみましょう (このコードはジェネリクスを説明するためだけのものであり、実際的な意味はありません) 何が間違っているのか見てみましょう。

リーリー コードを表示
    • まず第一に、ArrayList は非ジェネリック コレクションであり、必要なパラメーターはオブジェクトなので、他の型をコレクションにアセンブルした場合、コンパイル中にエラーを判断できません (たとえば、文字列をアセンブルした場合、正常にコンパイルできます)。
    • ArrayList にはオブジェクトが必要であるため、値型をコレクションにアセンブルするときに、コレクション内のデータを使用するときに暗黙的なボックス化操作が実行されます。アンボックス化操作が必要です。そのため、アプリケーションのパフォーマンスに影響します。 (値の型のボックス化については、「C# の参照型と値の型」を参照してください)。
    • コードの可読性は低いかもしれませんし、ArrayListにどのような型を持たせる必要があるのか​​全く不明です。
ジェネリックがこれらの問題をどのように解決するかを見てみましょう:

リーリー コードを表示
    • List は double のみをアセンブルできるため、他の型をアセンブルするとコンパイラはエラーを報告し、コードのセキュリティが向上します。
    • List は double 型でのみアセンブルできるため、これによりコンパイラの暗黙的なボックス化およびボックス化解除操作が回避され、アプリケーションのパフォーマンスが向上します。
    • 組み立てる必要があるコレクションの種類が一目でわかるため、コードの可読性が向上します。
    • ここでは NumberSqrt メソッドは役に立ちません。アルゴリズムを作成したい場合は、NumberSqrt をアルゴリズムの再利用が可能な汎用メソッド NumberSqrt に変更します。
3. ジェネリックスの使用方法: 構文とルール

ジェネリックとは何か、なぜジェネリックを使用する必要があるのか​​についてはすでに説明しました。次に、ジェネリックの使用方法について説明します。
    • 泛型的语法:泛型主要有泛型方法(例如:static void Swap(ref T lhs, ref T rhs)),泛型接口( 例如: public interface IEnumerable : IEnumerable),泛型委托( 例如: public delegate void Action(T obj);)
    • 在FCL中内建了许多泛型的接口定义(常用的接口都在System.Collections.Generic),为了使泛型能够正常的工作,微软的开发人员将会做如下工作,
      • 创建新的IL指令,使之能够识别类型参数。
      • 改变元数据的格式,使之能够识别泛型参数和泛型方法。
    • 开放类型和封闭类型:当为一个泛型类型没有指定实际的数据类型时,就称为开放类型,例如List,对于任何开放类型都不能创建该类型的实例,例如下面的代码在运行是将会报错未:经处理的异常:  System.ArgumentException: 无法创建 Consoleapplication2.Demo`1[T] 的实例,因为 Type.ContainsGenericParameters 为 True。
<span style="color: #008080;"> 1</span>     <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span> Demo<T><span style="color: #000000;"> { }
</span><span style="color: #008080;"> 2</span>     <span style="color: #0000ff;">class</span><span style="color: #000000;"> Program
</span><span style="color: #008080;"> 3</span> <span style="color: #000000;">    {
</span><span style="color: #008080;"> 4</span>         <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> Main(<span style="color: #0000ff;">string</span><span style="color: #000000;">[] args)
</span><span style="color: #008080;"> 5</span> <span style="color: #000000;">        {
</span><span style="color: #008080;"> 6</span>             <span style="color: #0000ff;">object</span> o = <span style="color: #0000ff;">null</span><span style="color: #000000;">;
</span><span style="color: #008080;"> 7</span>             Type t = <span style="color: #0000ff;">typeof</span>(Demo<><span style="color: #000000;">);
</span><span style="color: #008080;"> 8</span>             o =<span style="color: #000000;"> Activator.CreateInstance(t);
</span><span style="color: #008080;"> 9</span> <span style="color: #000000;">        }
</span><span style="color: #008080;">10</span>     }
ログイン後にコピー
View Code

               如果为泛型类型的所有类型实参传递的都是实际数据类型,类型就称为封闭类型,CLR允许构造封闭类型的实例。

    • 泛型类型和继承:泛型类型只是一个特殊的类型,所以它可以从其他任何类型派生。比如List是从object派生的,那么List也是从object派生的。换句话说,类型实参的指定和继承层次结构没有任何关系-理解这一点,有助于你判断那些转型是否能够进行,哪些转型是不能进行的。
    • 编译器如何解决“代码爆炸”的问题:在使用泛型类型或者方法方法时,Clr获取IL代码,用指定的类型实参进行替换。如果Clr为每种不同的类型/方法组合都生成不同的本地代码,他可能会增大应用程序的工作集,从而影响性能。我们将这种现象称为”代码爆炸“。那么编译器又是如何解决这个问题的呢?编译器认为所有引用类型的实参是完全相同的,所以代码能够共享(为什么这么说呢?是因为所有引用类型的实参或者变量实际只是指向堆上的对象的指针,而对象的指针全部是以相同的方式来操操纵的)。但是如果某个类型实参是值类型的话,CLR就得专门为那个值类型生成本地代码,这是值类型的大小不定。
    • 泛型接口:为什么要使用泛型接口呢?因为你每次试图使用一个非泛型接口来操作一个值类型时,都会发生装箱,从而失去编译时的类型安全性。所以CLR提供了对泛型接口的支持。下面来看一个例子:
      <span style="color: #008080;"> 1</span>    <span style="color: #008000;">//</span><span style="color: #008000;"> 摘要:
      </span><span style="color: #008080;"> 2</span>     <span style="color: #008000;">//</span><span style="color: #008000;">     支持在泛型集合上进行简单迭代。
      </span><span style="color: #008080;"> 3</span>     <span style="color: #008000;">//</span>
      <span style="color: #008080;"> 4</span>     <span style="color: #008000;">//</span><span style="color: #008000;"> 类型参数:
      </span><span style="color: #008080;"> 5</span>     <span style="color: #008000;">//</span><span style="color: #008000;">   T:
      </span><span style="color: #008080;"> 6</span>     <span style="color: #008000;">//</span><span style="color: #008000;">     要枚举的对象的类型。</span>
      <span style="color: #008080;"> 7</span>     <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">interface</span> IEnumerator<<span style="color: #0000ff;">out</span> T><span style="color: #000000;"> : IDisposable, IEnumerator
      </span><span style="color: #008080;"> 8</span> <span style="color: #000000;">    {
      </span><span style="color: #008080;"> 9</span>         <span style="color: #008000;">//</span><span style="color: #008000;"> 摘要:
      </span><span style="color: #008080;">10</span>         <span style="color: #008000;">//</span><span style="color: #008000;">     获取集合中位于枚举数当前位置的元素。
      </span><span style="color: #008080;">11</span>         <span style="color: #008000;">//</span>
      <span style="color: #008080;">12</span>         <span style="color: #008000;">//</span><span style="color: #008000;"> 返回结果:
      </span><span style="color: #008080;">13</span>         <span style="color: #008000;">//</span><span style="color: #008000;">     集合中位于枚举数当前位置的元素。</span>
      <span style="color: #008080;">14</span>         T Current { <span style="color: #0000ff;">get</span><span style="color: #000000;">; }
      </span><span style="color: #008080;">15</span>     }
      ログイン後にコピー
      View Code
    • 泛型委托:CLR之所以要支持泛型委托,主要目的是保证任何类型的对象都能以一种类型安全的方式传给一个回调方法,并且保证了将一个值类型实例传递给一个回调方法时不执行任何装箱处理。
    • 泛型方法:定义泛型类,结构或者接口时,这些类型中定义的任何方法都可引用由类型指定的一个类型参数。类型参数可以作为方法的参数/返回值,或者作为方法内部的一个局部变量来使用。下面来看一个例子:
      <span style="color: #008080;"> 1</span>  <span style="color: #0000ff;">class</span><span style="color: #000000;"> Program
      </span><span style="color: #008080;"> 2</span> <span style="color: #000000;">    {
      </span><span style="color: #008080;"> 3</span>         <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> Main(<span style="color: #0000ff;">string</span><span style="color: #000000;">[] args)
      </span><span style="color: #008080;"> 4</span> <span style="color: #000000;">        {
      </span><span style="color: #008080;"> 5</span>             MyClass<<span style="color: #0000ff;">string</span>> myclass = <span style="color: #0000ff;">new</span> MyClass<<span style="color: #0000ff;">string</span>><span style="color: #000000;">();
      </span><span style="color: #008080;"> 6</span>             myclass.ShowInfo(<span style="color: #800000;">"</span><span style="color: #800000;">myClass</span><span style="color: #800000;">"</span><span style="color: #000000;">);
      </span><span style="color: #008080;"> 7</span> <span style="color: #000000;">        }
      </span><span style="color: #008080;"> 8</span> <span style="color: #000000;">    }
      </span><span style="color: #008080;"> 9</span> 
      <span style="color: #008080;">10</span>     <span style="color: #0000ff;">class</span> MyClass<Ta>
      <span style="color: #008080;">11</span> <span style="color: #000000;">    {
      </span><span style="color: #008080;">12</span>         <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> ShowInfo(Ta a)
      </span><span style="color: #008080;">13</span> <span style="color: #000000;">        {
      </span><span style="color: #008080;">14</span>             Type t = <span style="color: #0000ff;">typeof</span><span style="color: #000000;">(Ta);
      </span><span style="color: #008080;">15</span>             Console.WriteLine(t.FullName + <span style="color: #800000;">"</span><span style="color: #800000;">非泛型方法</span><span style="color: #800000;">"</span><span style="color: #000000;">);
      </span><span style="color: #008080;">16</span> <span style="color: #000000;">        }
      </span><span style="color: #008080;">17</span>         <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> ShowInfo<Ta><span style="color: #000000;">(Ta a)
      </span><span style="color: #008080;">18</span> <span style="color: #000000;">        {
      </span><span style="color: #008080;">19</span>             Type t = <span style="color: #0000ff;">typeof</span><span style="color: #000000;">(Ta);
      </span><span style="color: #008080;">20</span>             Console.WriteLine(t.FullName + <span style="color: #800000;">"</span><span style="color: #800000;">泛型方法</span><span style="color: #800000;">"</span><span style="color: #000000;">);
      </span><span style="color: #008080;">21</span> <span style="color: #000000;">        }
      </span><span style="color: #008080;">22</span>         <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> ShowInfo<Tb><span style="color: #000000;">(Ta a, Tb b)
      </span><span style="color: #008080;">23</span> <span style="color: #000000;">        {
      </span><span style="color: #008080;">24</span>             Type t = <span style="color: #0000ff;">typeof</span><span style="color: #000000;">(Tb);
      </span><span style="color: #008080;">25</span> <span style="color: #000000;">            Console.WriteLine(t.FullName);
      </span><span style="color: #008080;">26</span> <span style="color: #000000;">        }
      </span><span style="color: #008080;">27</span>     }
      ログイン後にコピー
      View Code

       泛型方法的类型推断:c#语法中包含大量"<"和">"符号,所以导致代码的可读性和可维护性降低了,所以为了改变这种情况,c#编译器支持在调用一个方法时进行类型推断。例如下面的代码:myclass.ShowInfo("myClass", myclass);会调用ShowInfo(string a, string b);

    • 可验证性和约束:编译器在编译泛型代码时,会对它进行分析,以确保代码适用于当前已有或将来可能定义的任何类型。请查看下面的方法:
      <span style="color: #008080;">1</span>      <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> Boolean MethodTakingAnyType<T><span style="color: #000000;">(T o) {
      </span><span style="color: #008080;">2</span>             T temp =<span style="color: #000000;"> o;
      </span><span style="color: #008080;">3</span> <span style="color: #000000;">            Console.WriteLine(o.ToString());
      </span><span style="color: #008080;">4</span>             Boolean b =<span style="color: #000000;"> temp.Equals(o);
      </span><span style="color: #008080;">5</span>             <span style="color: #0000ff;">return</span><span style="color: #000000;"> b;
      </span><span style="color: #008080;">6</span>         }
      ログイン後にコピー
      View Code

      看这个方法里面有一个临时变量temp。方法里面执行两次变量赋值和几次方法调用,无论T是值类型还是引用类型还是接口类型或者是委托类型这个方法都能工作。这个方法适用于当前存在的所以类型,也适用于将来可能定义的任何类型。比如你再看一下下面这个方法:

      <span style="color: #008080;">1</span>      <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> T MethodTakingAnyType<T><span style="color: #000000;">(T o1,T o2) {
      </span><span style="color: #008080;">2</span>             <span style="color: #0000ff;">if</span> (o1.CompareTo(o2) < <span style="color: #800080;">0</span><span style="color: #000000;">)
      </span><span style="color: #008080;">3</span> <span style="color: #000000;">            {
      </span><span style="color: #008080;">4</span>                 <span style="color: #0000ff;">return</span><span style="color: #000000;"> o1;
      </span><span style="color: #008080;">5</span> <span style="color: #000000;">            }
      </span><span style="color: #008080;">6</span>             <span style="color: #0000ff;">else</span><span style="color: #000000;"> {
      </span><span style="color: #008080;">7</span>                 <span style="color: #0000ff;">return</span><span style="color: #000000;"> o2;
      </span><span style="color: #008080;">8</span> <span style="color: #000000;">            }
      </span><span style="color: #008080;">9</span>         }
      ログイン後にコピー
      View Code

      在编译时会报如下错误(error CS0117:"T"不包含"CompareTo"的定义),因为并不是所有的类型都提供了CompareTo方法。那么在什么情况下T应该是什么类型呢?幸好,编译器和CLR支持一个称为约束的机制,可利用它使泛型变得真正有用!

      • 约束的语法:请看下面这个方法名字后面的where
        <span style="color: #008080;">1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> Boolean MethodTakingAnyType<T>(T o) <span style="color: #0000ff;">where</span> T : <span style="color: #0000ff;">struct</span>
        <span style="color: #008080;">2</span> <span style="color: #000000;">        {
        </span><span style="color: #008080;">3</span>             T temp =<span style="color: #000000;"> o;
        </span><span style="color: #008080;">4</span> <span style="color: #000000;">            Console.WriteLine(o.ToString());
        </span><span style="color: #008080;">5</span>             Boolean b =<span style="color: #000000;"> temp.Equals(o);
        </span><span style="color: #008080;">6</span>             <span style="color: #0000ff;">return</span><span style="color: #000000;"> b;
        </span><span style="color: #008080;">7</span>         }
        ログイン後にコピー
        View Code

        c#的where关键字告诉编译器,为T指定的任何类型都必须是值类型。所以当你为T指定其它类型时,编译器会报错,例如你指定为string类型时(MethodTakingAnyType("");)它会报错误    1    类型“string”必须是不可以为 null 值的类型才能用作泛型类型或方法

      • 主要约束:一个类型参数可以指定零个或一个主要约束(也就是第一个约束)。主要约束可以是一个引用类型,它标识了一个没有密封的类,不能指定以下特殊类型:System.Object;System.Array;System.Delegate;System.MulticastDelegate;System.ValueType;System.Enum;
      • 次要约束:一个类型参数可以指定零个或多个次要约束。次要约束是一个接口类型,指定一个接口类型约束时,是告诉编译器类型实参必须实现这个接口。还有一种次要约束称为类型参数约束,有时也称为裸类型约束,看下面的代码
        <span style="color: #008080;">1</span>  <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> Boolean MethodTakingAnyType<T, TBase>(T o) <span style="color: #0000ff;">where</span><span style="color: #000000;"> T : TBase
        </span><span style="color: #008080;">2</span> <span style="color: #000000;">        {
        </span><span style="color: #008080;">3</span>             T temp =<span style="color: #000000;"> o;
        </span><span style="color: #008080;">4</span> <span style="color: #000000;">            Console.WriteLine(o.ToString());
        </span><span style="color: #008080;">5</span>             Boolean b =<span style="color: #000000;"> temp.Equals(o);
        </span><span style="color: #008080;">6</span>             <span style="color: #0000ff;">return</span><span style="color: #000000;"> b;
        </span><span style="color: #008080;">7</span>         }
        ログイン後にコピー
        View Code

        T类型参数由TBase类型单数约束,也就是说不管T为什么类型,都必须兼容于TBase指定的类型实参。

      • 构造器约束:一个类型参数可以指定零个或一个构造器约束。指定构造器约束相当于告诉编译器一个指定的类型实参必须要实现了公共无参构造器。请看下面的的代码:
        <span style="color: #008080;">1</span>       <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> Boolean MethodTakingAnyType<T, TBase>(T o) <span style="color: #0000ff;">where</span> T : <span style="color: #0000ff;">new</span><span style="color: #000000;">()
        </span><span style="color: #008080;">2</span> <span style="color: #000000;">        {
        </span><span style="color: #008080;">3</span>             T temp =<span style="color: #000000;"> o;
        </span><span style="color: #008080;">4</span> <span style="color: #000000;">            Console.WriteLine(o.ToString());
        </span><span style="color: #008080;">5</span>             Boolean b =<span style="color: #000000;"> temp.Equals(o);
        </span><span style="color: #008080;">6</span>             <span style="color: #0000ff;">return</span><span style="color: #000000;"> b;
        </span><span style="color: #008080;">7</span>         }
        ログイン後にコピー
        View Code
      • 其它可验证性的问题:1,泛型类型变量的转型。下面的类型编译时出错,因为T可能为任何类型,无法保证能转型成功!
        <span style="color: #008080;">1</span>     <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> MethodTakingAnyType<T, TBase><span style="color: #000000;">(T o)
        </span><span style="color: #008080;">2</span> <span style="color: #000000;">        {
        </span><span style="color: #008080;">3</span>             <span style="color: #0000ff;">int</span> x = (<span style="color: #0000ff;">int</span><span style="color: #000000;">)o;
        </span><span style="color: #008080;">4</span>             <span style="color: #0000ff;">string</span> s = (<span style="color: #0000ff;">string</span><span style="color: #000000;">)o;
        </span><span style="color: #008080;">5</span>         }
        ログイン後にコピー
        View Code

        2,将一个泛型类型变量设置为默认值:o = default(T);这样的话,不管T为值类型还是引用类型都可以成功,如果T为引用类型时就设置为null,如果T为值类型时将默认值设为0;

        3,将一个泛型类型变量与Null进行比较:使用==或者=!将一个泛型类型变量于null进行比较都是合法的。但是如果T为值类型时,o永远都不会为null查看下面的代码:
        <span style="color: #008080;">1</span>        <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> MethodTakingAnyType<T, TBase><span style="color: #000000;">(T o)
        </span><span style="color: #008080;">2</span> <span style="color: #000000;">        {
        </span><span style="color: #008080;">3</span>             <span style="color: #0000ff;">if</span> (o == <span style="color: #0000ff;">null</span><span style="color: #000000;">) { 
        </span><span style="color: #008080;">4</span>             <span style="color: #008000;">//</span><span style="color: #008000;">do something</span>
        <span style="color: #008080;">5</span> <span style="color: #000000;">            }
        </span><span style="color: #008080;">6</span>         }
        ログイン後にコピー
        View Code

        4,将两个泛型类型变量相互比较:如果T是值类型,下面的代码就是非法的。

        <span style="color: #008080;">1</span>    <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> MethodTakingAnyType<T><span style="color: #000000;">(T o1,T o2)
        </span><span style="color: #008080;">2</span> <span style="color: #000000;">        {
        </span><span style="color: #008080;">3</span>             <span style="color: #0000ff;">if</span> (o1 ==<span style="color: #000000;"> o2) { 
        </span><span style="color: #008080;">4</span>             
        <span style="color: #008080;">5</span> <span style="color: #000000;">            }
        </span><span style="color: #008080;">6</span>         }
        ログイン後にコピー
        View Code

     4,泛型在使用过程的注意事项

    • 泛型不支持协变性
    • 缺乏操作符约束或者“数值”约束
    • 缺乏泛型属性,索引器和其它成员类型

 


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のおすすめ
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート