具象化とは何ですか?


163

Javaは消去を伴うパラメトリックポリモーフィズム(ジェネリック)を実装することを知っています。消去とは何ですか。

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

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

または、いくつかのパラメーター化された型の型パラメーターが何であるかを実行時に知ることができますが、それ何であるかはわかりません。

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

それは答えではありませんが、何らかの
heringer

「消去とは何か」という質問にかなりよく答えるようで、基本的に「具体化とは何か」に「消去ではない」と答えるように見える@heringer-ここに投稿する前に最初に回答を検索したときに見つけた共通のテーマ。
Martijn

5
...そして、再構成は以前に/ からaに変換されていたときに、構造体を/ ifに変換するプロセスであると私は思っていました...switchifelseifelseswitch
Digital Trauma

8
Resreis物事のラテン語ので、具体は文字通り物事です。C#でのこの用語の使用に関しては、私が貢献できることは何もありませんが、それ自体がそれを使用したという事実は、私を笑顔にします。
KRyan 2015

回答:


209

具象化は、抽象的なものを取り、具体的なものを作成するプロセスです。

C#ジェネリックでの具体という用語は、ジェネリック型定義と1つ以上のジェネリック型引数(抽象的なもの)を組み合わせて新しいジェネリック型(具体的なもの)を作成するプロセスを指します。

フレーズにそれは違った、それはの定義を取る過程であるList<T>intし、具体的な生産List<int>タイプを。

さらに理解するには、次のアプローチを比較してください。

  • Javaジェネリックでは、ジェネリック型定義は、許可されているすべての型引数の組み合わせで共有される基本的に1つの具象ジェネリック型に変換されます。したがって、複数の(ソースコードレベル)型が1つの(バイナリレベル)型にマップされますが、その結果、インスタンスの型引数に関する情報は、そのインスタンスでは破棄されます(型消去)

    1. この実装手法の副作用として、ネイティブに許可される唯一のジェネリック型引数は、具象型のバイナリコードを共有できる型です。これは、格納場所が交換可能な表現を持つタイプを意味します。これは参照型を意味します。ジェネリック型の引数として値型を使用するには、それらをボックス化する必要があります(単純な参照型ラッパーに配置する)。
    2. この方法でジェネリックスを実装するためにコードが複製されることはありません。
    3. 実行時に(リフレクションを使用して)入手できた可能性のあるタイプ情報は失われます。つまり、ジェネリック型の特殊化(特殊化されたソースコードを使用する機能)特定のジェネリック引数の組み合わせに対して)が非常に制限さます。
    4. このメカニズムは、ランタイム環境からのサポートを必要としません。
    5. JavaプログラムまたはJVMベースの言語が使用できるタイプ情報を保持するためのいくつかの回避策があります。
  • C#ジェネリックでは、ジェネリック型定義は実行時にメモリに保持されます。新しい具象型が必要になると、ランタイム環境はジェネリック型定義と型引数を組み合わせて新しい型(具体化)を作成します。したがって、実行時に型引数の各組み合わせに対して新しい型を取得します

    1. この実装手法では、あらゆる種類の型引数の組み合わせをインスタンス化できます。ジェネリック型引数として値型を使用しても、これらの型は独自の実装を取得するため、ボクシングは発生しません。(もちろん、ボクシングはまだC#存在しますが、これは他のシナリオで発生します。このシナリオでは発生しません。)
    2. コードの重複が問題になる可能性がありますが、実際には問題はありません。十分にスマートな実装(これにはMicrosoft .NETMonoが含まれるため))は、いくつかのインスタンス化のためにコードを共有できるためです。
    3. リフレクションを使用して型引数を調べることにより、型情報が維持され、ある程度まで特殊化できます。ただし、具体化が発生する前にジェネリック型定義がコンパイルされるという事実の結果として、特殊化の程度は制限されます(これは、型パラメーターの制約に対して定義をコンパイルすることによって行われるため、コンパイラーは、特定の型引数がない場合でも定義を「理解」する)。
    4. この実装手法は、ランタイムサポートとJITコンパイルに大きく依存します(そのため、iOSのようなプラットフォームではC#ジェネリックにはいくつかの制限があるとよく耳にします、動的コードの生成が制限されてなどの)。
    5. C#ジェネリックのコンテキストでは、具体化はランタイム環境によって行われます。ただし、ジェネリック型定義と具象ジェネリック型の違いをより直感的に理解したい場合はSystem.Typeクラスを使用して、いつでも自分で具体化を行うことができます(インスタンス化している特定のジェネリック型の引数の組み合わせがしなかったとしても) tソースコードに直接表示されます)。
  • C ++テンプレートでは、テンプレート定義はコンパイル時にメモリに保持されます。ソースコードでテンプレートタイプの新しいインスタンス化が必要な場合は常に、コンパイラーはテンプレート定義とテンプレート引数を組み合わせて新しいタイプを作成します。したがって、コンパイル時に、テンプレート引数の各組み合わせに対して一意の型を取得します

    1. この実装手法では、あらゆる種類の型引数の組み合わせをインスタンス化できます。
    2. これはバイナリコードを複製することが知られていますが、十分にスマートなツールチェーンがこれを検出し、いくつかのインスタンス化のためにコードを共有できます。
    3. テンプレート定義自体は「コンパイル」されていません。具体的なインスタンス化のみが実際にコンパイルされています。これにより、コンパイラーへの制約が少なくなり、より高度なテンプレート特殊化が可能になります
    4. テンプレートのインスタンス化はコンパイル時に実行されるため、ここでもランタイムサポートは必要ありません。
    5. このプロセスは、特にRustコミュニティでは最近、単形化と呼ばれています。この言葉は、ジェネリックの由来となる概念の名前であるパラメトリック多態性とは対照的に使用されます。

7
C ++テンプレートとの優れた比較...それらはC#とJavaのジェネリックの間のどこかにあるようです。C#のようなさまざまな特定のジェネリック型を処理するためのさまざまなコードと構造がありますが、すべてJavaのようにコンパイル時に行われます。
Luaan

3
また、C ++では、これにより、テンプレートの特殊化を導入できます。各(または一部の)具象型は、異なる実装を持つことができます。明らかにJavaでは不可能ですが、C#では不可能です。
ケツァルコアトル2015

@quetzalcoatlを使用する理由の1つは、ポインター型を使用して生成されるコードの量を減らすことですが、C#は舞台裏で参照型と同等のことを行います。それでも、それがそれを使用する理由の1つにすぎません。また、テンプレートの特殊化が優れていることは間違いありません。
Jon Hanna

Javaの場合、型情報が消去されている間、コンパイラーによってキャストが追加されるため、バイトコードがジェネリック以前のバイトコードと区別がつかないようにすることができます。
Rusty Core

27

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

プログラミングでは、言語自体でそれに関する情報にアクセスできれば、何かが具体化されます。

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

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

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

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

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

一つは、あるList<string>だけのようなタイプであるstringintです。そのタイプを比較して名前を取得し、それについて問い合わせることができます。

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メソッド内で「本物」である、この比較は、ランタイムまたはjitting時のいずれか行うことができます(一般的に時間をjitting、確かに上記の場合は、jitting時にそうして取られていないパスのマシンコードを生成しない)、我々は持っていますケースごとにメソッドの個別の「実際の」バージョン。(最適化として、マシンコードは異なる参照型の型パラメーターのさまざまなメソッドで共有されます。これは、これに影響を与えずに実行できるため、ジッターされるマシンコードの量を減らすことができるためです)。

C#で私たちは当たり前のこの具体化を取るので、あなたはまた、Javaの対処しない限り、(これは、C#でジェネリック型の具体化について話をするのが一般的ではありません。すべてのタイプが具体化されているのJavaは、非ジェネリック型はと呼ばれている。具体化そのため、それらとジェネリック型の違いです)。


あなたMinは、上記のことを役立てることができないと思いますか?そうでなければ、文書化された動作を実行することは非常に困難です。
Jon Hanna

私はバグを(非)文書化された動作と見なし、その動作が有用であることを意味します(余談ですが、の動作はEnumerable.Min<TSource>、空のコレクションで非参照型をスローしないという点で異なりますが、デフォルトを返します(TSource)、そして「一般的なシーケンスの最小値を返す」としてのみ文書化されています。両方とも空のコレクションをスローする必要があるか、または「ゼロ」要素がベースラインとして渡され、コンパレータ/比較関数は常に渡される必要があります)
Martijn 2015

1
これは、現在のMinよりもはるかに有用ではありません。現在のMinは、null可能でない型で不可能を試みずにnull可能型での一般的なdbの動作に一致します。(ベースラインのアイデアは不可能ではありませんが、ソースに決してないことがわかる値がない限り、あまり役に立ちません)。
Jon Hanna

1
これには、Thingificationの方が適しています。:)
tchrist

@tchrist物事は非現実的である場合があります。
Jon Hanna

15

duffymoすでに指摘し、「具象化」は重要な違いではありません。

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

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

これは、ジェネリックが実際に機能する方法にいくつかの影響があります。例えば:

  • JavaではSomeType<?>、特定のジェネリック型の具体的な実装を渡すことができるようにする必要があります。C#はこれを実行できません。特定の(具体化された)ジェネリック型はすべて独自の型です。
  • Javaの無制限のジェネリック型は、その値がとして格納されることを意味しますobject。このようなジェネリックで値型を使用すると、パフォーマンスに影響を与える可能性があります。C#では、ジェネリック型で値型を使用すると、値型のままになります。

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

C#ではList<string>List<int>2つの異なるタイプです。これらは互換性がなく、型の安全性は実行時にも適用されます。あなたは何をすべきかの問題、何new List<int>().Add("SomeString")だろう決して仕事ない-内の基礎記憶域がList<int>あり、本当に Javaで、それは必然的である一方で、いくつかの整数配列objectの配列。C#では、キャストやボクシングなどは必要ありません。

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

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


2
まあ、C#では型List<>などを利用できますDictionary<,>が、それと特定の具体的なリストや辞書との間のギャップを埋めるにはかなりの反省が必要です。インターフェースの分散は、そのギャップを簡単に埋めたいと思ったこともあるが、すべてではない場合に役立ちます。
Jon Hanna

2
@JonHannaを使用List<>して、新しい特定のジェネリック型をインスタンス化できますが、それでも、必要な特定の型を作成する必要があります。ただしList<>、たとえば、引数として使用することはできません。しかし、はい、少なくともこれにより、反射を使用してギャップを埋めることができます。
Luaan

.NET Frameworkには、ストレージロケーションタイプではない3つのハードコーディングされた一般的な制約があります。他のすべての制約は、保管場所タイプでなければなりません。さらに、ジェネリック型Tがstorage-location-type制約を満たすことができるのUは、TUが同じ型であるかU、のインスタンスへの参照を保持できる型である場合のみですT。タイプの格納場所を意味のある形で持つことは不可能ですSomeType<?>が、理論的にはそのタイプの一般的な制約を持つことは可能です。
スーパーキャット2015

1
コンパイルされたJavaバイトコードにジェネリックの概念がないことは真実ではありません。クラスのインスタンスにジェネリックスの概念がないだけです。これは重要な違いです。これについては、programmers.stackexchange.com / questions / 280169 /… で以前に書いたことがあります。
ruakh

2

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

Reifyは、「抽象的なものを現実のものにするという意味の動詞です。

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

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


2
具象化は、「何かを現実にする」という一般的な概念であり、あなたが言うようにオブジェクト指向モデリングには当てはまりますが、ジェネリックスの実装のコンテキストでも意味があります。
Jon Hanna

2
だから私はこれらの答えを読むことによって教育を受けてきました。答えを修正します。
duffymo 2015

2
この答えは、ジェネリックとパラメトリック多態性に対するOPの関心に対処するものではありません。
エリックG.ハグストロム2015

このコメントは、誰かの関心に対応したり、担当者を後押ししたりするものではありません。あなたは何も提供しなかったようです。鉱山が最初の答えでした、そしてそれは具体化をより広いものとして定義しました。
duffymo 2015

1
あなたの答えが最初だったかもしれませんが、OPが尋ねた質問ではなく、別の質問に答えました。これは、質問の内容とそのタグから明らかでした。回答を作成する前に質問を完全に読んでいないか、「具体化」という用語がジェネリックのコンテキストで確立された意味を持っていることを知らなかったのかもしれません。どちらにしても、あなたの答えは役に立ちません。反対票。
jcsahnwaldt Reinstate Monica
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.