Javaは消去を伴うパラメトリック多相性(ジェネリック)を実装しています。消去とは何かを理解しています。
C#は具体化を伴うパラメトリック多相性を実装することを知っています。私はそれがあなたを書くことができることを知っています
public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}
または、パラメータ化された型の型パラメータが実行時にわかるが、それが何なのか理解できませんis。
具体化は、一般に(コンピューターサイエンス以外で)「何かを現実にする」ことを意味します。
プログラミングでは、言語自体でそれに関する情報にアクセスできる場合、何かは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では、非ジェネリック型は具体化これは、それらとジェネリック型との違いだからです)。
duffymo既に述べた のように、「具体化」は重要な違いではありません。
Javaでは、ジェネリックは基本的にコンパイル時のサポートを改善するためにあります。コード内のコレクション、およびタイプセーフが処理されます。ただし、これはコンパイル時にのみ存在します。コンパイルされたバイトコードにはジェネリックの概念がなくなりました。すべてのジェネリック型は「具象」型に変換され(ジェネリック型が無制限の場合はobject
を使用)、必要に応じて型変換と型チェックを追加します。
.NETでは、ジェネリックはCLRの不可欠な機能です。ジェネリック型をコンパイルすると、生成されたILでジェネリック型のままになります。 Javaのように非汎用コードに変換されるだけではありません。
これは、ジェネリックが実際にどのように機能するかにいくつかの影響を及ぼします。例えば:
SomeType<?>
があり、特定のジェネリック型の具体的な実装を渡すことができます。 C#はこれを行うことができません-すべての特定の(reified)ジェネリック型は独自の型です。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>();
はできません) string
はobject
から派生していますが)。
どちらの実装にも長所と短所があります。 C#の引数としてSomeType<?>
を許可できるようになりたいと思ったことが何度かありましたが、C#ジェネリックが機能する方法には意味がありません。
具体化は、オブジェクト指向モデリングの概念です。
Reifyは、 "何かを抽象化する" を意味する動詞です。
オブジェクト指向プログラミングを行う場合、現実のオブジェクトをソフトウェアコンポーネント(ウィンドウ、ボタン、人物、銀行、乗り物など)としてモデル化するのが一般的です
また、抽象概念をコンポーネントに具体化することも一般的です(例:WindowListener、Brokerなど)