web-dev-qa-db-ja.com

具体化とは何ですか?

Javaは消去を伴うパラメトリック多相性(ジェネリック)を実装しています。消去とは何かを理解しています。

C#は具体化を伴うパラメトリック多相性を実装することを知っています。私はそれがあなたを書くことができることを知っています

public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}

または、パラメータ化された型の型パラメータが実行時にわかるが、それが何なのか理解できませんis

  • 具象型とは何ですか?
  • 具体化された値とは何ですか?
  • 型/値が具体化されるとどうなりますか?
155
Martijn

具体化は、一般に(コンピューターサイエンス以外で)「何かを現実にする」ことを意味します。

プログラミングでは、言語自体でそれに関する情報にアクセスできる場合、何かはreifiedです。

C#が具体化したものと具体化していないものの2つの完全に非ジェネリック関連の例については、メソッドとメモリアクセスを見てみましょう。

OO言語には通常、メソッドがあります(クラスにバインドされていないが似ている関数を持たない多くの言語)。そのため、そのような言語でメソッドを定義したり、呼び出したり、オーバーライドしたりできます。このような言語のすべてが、メソッド自体をプログラムのデータとして実際に処理できるわけではありません。 C#(実際にはC#ではなく.NET)を使用すると、メソッドを表すMethodInfoオブジェクトを使用できるため、C#のメソッドが具体化されます。 C#のメソッドは「ファーストクラスオブジェクト」です。

すべての実用的な言語には、コンピューターのメモリにアクセスする手段があります。 Cのような低レベル言語では、コンピューターが使用する数値アドレス間のマッピングを直接処理できるので、int* ptr = (int*) 0xA000000; *ptr = 42;のようなものは合理的です(アクセスを疑う十分な理由がある限り)メモリアドレス0xA000000この方法では、何かが爆発することはありません)。 C#ではこれは合理的ではありません(.NETでそれを強制することはできますが、.NETのメモリ管理では、物事を移動することはあまり有用ではありません)。 C#には具体化されたメモリアドレスがありません。

したがって、refiedは「現実化」を意味するため、「具体化された型」は、問題の言語で「話す」ことができる型です。

ジェネリックでは、これは2つのことを意味します。

1つはList<string>は、stringまたはintと同じタイプです。そのタイプを比較し、その名前を取得して、問い合わせることができます。

Console.WriteLine(typeof(List<string>).FullName); // System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Console.WriteLine(typeof(List<string>) == (42).GetType()); // False
Console.WriteLine(typeof(List<string>) == Enumerable.Range(0, 1).Select(i => i.ToString()).ToList().GetType()); // True
Console.WriteLine(typeof(List<string>).GenericTypeArguments[0] == typeof(string)); // True

この結果、メソッド自体内でジェネリックメソッド(またはジェネリッククラスのメソッド)のパラメーターの型を "話す"ことができます。

public static void DescribeType<T>(T element)
{
  Console.WriteLine(typeof(T).FullName);
}
public static void Main()
{
  DescribeType(42);               // System.Int32
  DescribeType(42L);              // System.Int64
  DescribeType(DateTime.UtcNow);  // System.DateTime
}

原則として、これをやりすぎると「臭い」のですが、多くの便利なケースがあります。たとえば、次をご覧ください。

public static TSource Min<TSource>(this IEnumerable<TSource> source)
{
  if (source == null) throw Error.ArgumentNull("source");
  Comparer<TSource> comparer = Comparer<TSource>.Default;
  TSource value = default(TSource);
  if (value == null)
  {
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
      do
      {
        if (!e.MoveNext()) return value;
        value = e.Current;
      } while (value == null);
      while (e.MoveNext())
      {
        TSource x = e.Current;
        if (x != null && comparer.Compare(x, value) < 0) value = x;
      }
    }
  }
  else
  {
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
      if (!e.MoveNext()) throw Error.NoElements();
      value = e.Current;
      while (e.MoveNext())
      {
        TSource x = e.Current;
        if (comparer.Compare(x, value) < 0) value = x;
      }
    }
  }
  return value;
}

これはTSourceの型とさまざまな動作のさまざまな型(一般的にはジェネリックをまったく使用すべきではないという兆候)の間の多くの比較を行いませんが、 null(要素が見つからない場合はnullを返す必要があり、比較する要素の1つがnullである場合は最小値を見つけるために比較してはいけません)およびコードパスnullにできない型(要素が見つからない場合はスローする必要があり、null要素の可能性を心配する必要はありません)。

TSourceはメソッド内で「本物」であるため、この比較は実行時またはジッター時に行うことができます(一般にジッター時、確かに上記のケースはジッター時に行い、パスのマシンコードを生成しません取られた)そして、私たちはそれぞれの場合にメソッドの別個の「実際の」バージョンを持っています。 (最適化として、マシンコードは異なる参照型のパラメータの異なるメソッドで共有されます。これは、これに影響を与えることなくできるため、マシンコードのジッタ量を減らすことができるためです)。

(C#では、この具体化を当然のことと考えているため、C#でジェネリック型の具体化について話すことは一般的ではありません。すべての型が具体化されます。Javaでは、非ジェネリック型は具体化これは、それらとジェネリック型との違いだからです)。

26
Jon Hanna

duffymo既に述べた のように、「具体化」は重要な違いではありません。

Javaでは、ジェネリックは基本的にコンパイル時のサポートを改善するためにあります。コード内のコレクション、およびタイプセーフが処理されます。ただし、これはコンパイル時にのみ存在します。コンパイルされたバイトコードにはジェネリックの概念がなくなりました。すべてのジェネリック型は「具象」型に変換され(ジェネリック型が無制限の場合はobjectを使用)、必要に応じて型変換と型チェックを追加します。

.NETでは、ジェネリックはCLRの不可欠な機能です。ジェネリック型をコンパイルすると、生成されたILでジェネリック型のままになります。 Javaのように非汎用コードに変換されるだけではありません。

これは、ジェネリックが実際にどのように機能するかにいくつかの影響を及ぼします。例えば:

  • JavaにはSomeType<?>があり、特定のジェネリック型の具体的な実装を渡すことができます。 C#はこれを行うことができません-すべての特定の(reified)ジェネリック型は独自の型です。
  • Javaの無制限のジェネリック型は、それらの値がobjectとして保存されることを意味します。ジェネリック型の値型、値型のままです。

サンプルを提供するために、1つのジェネリック引数を持つListジェネリック型があるとします。 Javaでは、List<String>List<Int>は実行時にまったく同じ型になります-ジェネリック型は実際にはコンパイル時コードにのみ存在します。へのすべての呼び出しGetValueは、それぞれ(String)GetValueおよび(Int)GetValueに変換されます。

C#では、List<string>List<int>は2つの異なるタイプです。これらは互換性がなく、型安全性も実行時に適用されます。何をしても、new List<int>().Add("SomeString")never動作します-List<int>の基礎となるストレージはreally整数配列。Javaでは、必ずobject配列です。 C#では、キャストやボクシングなどは関係ありません。

これはまた、C#がJava with SomeType<?>と同じことをできない理由を明らかにする必要があります。Javaでは、すべてのジェネリック型はSomeType<?>から派生します] C#では、さまざまな特定のSomeType<T>はすべて独自の個別の型です。コンパイル時のチェックを削除すると、SomeType<Int>の代わりにSomeType<String>を渡すことができます(実際には、 SomeType<?>が意味することはすべて、「指定されたジェネリック型のコンパイル時チェックを無視する」ことです。)C#では、派生型であっても不可能です(つまり、List<object> list = (List<object>)new List<string>();はできません) stringobjectから派生していますが)。

どちらの実装にも長所と短所があります。 C#の引数としてSomeType<?>を許可できるようになりたいと思ったことが何度かありましたが、C#ジェネリックが機能する方法には意味がありません。

14
Luaan

具体化は、オブジェクト指向モデリングの概念です。

Reifyは、 "何かを抽象化する" を意味する動詞です。

オブジェクト指向プログラミングを行う場合、現実のオブジェクトをソフトウェアコンポーネント(ウィンドウ、ボタン、人物、銀行、乗り物など)としてモデル化するのが一般的です

また、抽象概念をコンポーネントに具体化することも一般的です(例:WindowListener、Brokerなど)

2
duffymo