Was ist Verdinglichung?

163

Ich weiß, dass Java parametrischen Polymorphismus (Generics) mit Löschung implementiert. Ich verstehe, was Löschen ist.

Ich weiß, dass C # parametrischen Polymorphismus mit Reifizierung implementiert. Ich weiß, das kann dich zum Schreiben bringen

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

oder dass Sie zur Laufzeit wissen können, was der Typparameter eines parametrisierten Typs ist, aber ich verstehe nicht, was es ist .

  • Was ist ein reifizierter Typ?
  • Was ist ein verdichteter Wert?
  • Was passiert, wenn ein Typ / Wert geändert wird?
Martijn
quelle
Es ist keine Antwort, aber helfen kann someway: beust.com/weblog/2011/07/29/erasure-vs-reification
Heringer
@heringer, der die Frage "Was ist Löschung?" ziemlich gut zu beantworten scheint und "Was ist Verdinglichung?" im Grunde mit "Nichtlöschung" zu beantworten scheint - ein häufiges Thema, das ich gefunden habe, als ich vor dem Posten hier nach einer Antwort gesucht habe.
Martijn
5
... und ich dachte, ifReplikation ist der Prozess der Konvertierung eines switchKonstrukts zurück in ein if/ else, als es zuvor von einem if/ elsein ein konvertiert worden war switch...
Digitales Trauma
8
Res , reis ist lateinisch für Ding , also ist Reifikation buchstäblich Dingifikation . Ich habe nichts Nützliches beizutragen, was C #s Verwendung des Begriffs betrifft, aber die Tatsache an und für sich, dass sie ihn verwendet haben, bringt mich zum Lächeln.
KRyan

Antworten:

209

Reifikation ist der Prozess, eine abstrakte Sache zu nehmen und eine konkrete Sache zu schaffen.

Der Begriff Reifizierung in C # -Generika bezieht sich auf den Prozess, durch den eine generische Typdefinition und ein oder mehrere generische Typargumente (das abstrakte Ding) kombiniert werden, um einen neuen generischen Typ (das konkrete Ding) zu erstellen .

Begriff anders es, ist es der Prozess der Übernahme der Definition List<T>und intund eine Herstellung von Beton - List<int>Typ.

Vergleichen Sie die folgenden Ansätze, um es besser zu verstehen:

  • In Java-Generika wird eine generische Typdefinition in im Wesentlichen einen konkreten generischen Typ umgewandelt, der von allen zulässigen Typargumentkombinationen gemeinsam genutzt wird. Daher werden mehrere Typen (Quellcodeebene) einem Typ (Binärebene) zugeordnet . Infolgedessen werden Informationen zu den Typargumenten einer Instanz in dieser Instanz verworfen (Typlöschung) .

    1. Als Nebeneffekt dieser Implementierungstechnik sind nur solche generischen Typargumente zulässig, die den Binärcode ihres konkreten Typs gemeinsam nutzen können. was bedeutet, dass diejenigen Typen, deren Speicherorte austauschbare Darstellungen haben; was Referenztypen bedeutet. Die Verwendung von Werttypen als generische Typargumente erfordert ein Boxen (Platzieren in einem einfachen Referenztyp-Wrapper).
    2. Es wird kein Code dupliziert, um Generika auf diese Weise zu implementieren.
    3. Typinformationen, die zur Laufzeit möglicherweise verfügbar waren (mithilfe von Reflection), gehen verloren. Dies bedeutet wiederum, dass die Spezialisierung eines generischen Typs (die Möglichkeit, speziellen Quellcode für eine bestimmte generische Argumentkombination zu verwenden) sehr eingeschränkt ist.
    4. Dieser Mechanismus erfordert keine Unterstützung durch die Laufzeitumgebung.
    5. Es gibt einige Problemumgehungen, um Typinformationen beizubehalten , die ein Java-Programm oder eine JVM-basierte Sprache verwenden kann.
  • In C # -Generics wird die generische Typdefinition zur Laufzeit im Speicher beibehalten. Immer wenn ein neuer konkreter Typ erforderlich ist, kombiniert die Laufzeitumgebung die generische Typdefinition und die Typargumente und erstellt den neuen Typ (Reifizierung). Daher erhalten wir zur Laufzeit für jede Kombination der Typargumente einen neuen Typ .

    1. Mit dieser Implementierungstechnik kann jede Art von Typargumentkombination instanziiert werden. Die Verwendung von Werttypen als generische Typargumente verursacht kein Boxen, da diese Typen eine eigene Implementierung erhalten. ( Boxen gibt es natürlich immer noch in C # - aber es passiert in anderen Szenarien, nicht in diesem.)
    2. Das Duplizieren von Code könnte ein Problem sein - in der Praxis jedoch nicht, da ausreichend intelligente Implementierungen ( einschließlich Microsoft .NET und Mono ) Code für einige Instanziierungen gemeinsam nutzen können.
    3. Typinformationen werden beibehalten, was eine gewisse Spezialisierung ermöglicht, indem Typargumente mithilfe von Reflexion untersucht werden. Der Spezialisierungsgrad ist jedoch begrenzt, da eine generische Typdefinition kompiliert wird, bevor eine erneute Überprüfung erfolgt (dies erfolgt durch Kompilieren der Definition anhand der Einschränkungen für die Typparameter - daher muss der Compiler in der Lage sein "verstehen" die Definition auch ohne spezifische Typargumente ).
    4. Diese Implementierungstechnik hängt stark von der Laufzeitunterstützung und der JIT-Kompilierung ab (weshalb Sie häufig hören, dass C # -Generics auf Plattformen wie iOS , auf denen die dynamische Codegenerierung eingeschränkt ist, einige Einschränkungen aufweisen ).
    5. Im Kontext von C # -Generics wird die Überprüfung von der Laufzeitumgebung für Sie durchgeführt. Wenn Sie jedoch den Unterschied zwischen einer generischen Typdefinition und einem konkreten generischen Typ intuitiver verstehen möchten, können Sie mithilfe der System.TypeKlasse jederzeit selbst eine Überprüfung durchführen (selbst wenn die von Ihnen instanziierte bestimmte generische Typargumentkombination dies nicht getan hat). t direkt in Ihrem Quellcode erscheinen).
  • In C ++ - Vorlagen wird die Vorlagendefinition zur Kompilierungszeit im Speicher beibehalten. Immer wenn im Quellcode eine neue Instanziierung eines Vorlagentyps erforderlich ist, kombiniert der Compiler die Vorlagendefinition und die Vorlagenargumente und erstellt den neuen Typ. So erhalten wir zur Kompilierungszeit für jede Kombination der Vorlagenargumente einen eindeutigen Typ .

    1. Mit dieser Implementierungstechnik kann jede Art von Typargumentkombination instanziiert werden.
    2. Es ist bekannt, dass dies Binärcode dupliziert, aber eine ausreichend intelligente Toolkette könnte dies immer noch erkennen und Code für einige Instanziierungen gemeinsam nutzen.
    3. Die Vorlagendefinition selbst wird nicht "kompiliert" - nur die konkreten Instanziierungen werden tatsächlich kompiliert . Dies stellt weniger Einschränkungen für den Compiler dar und ermöglicht einen höheren Grad an Vorlagenspezialisierung .
    4. Da Vorlageninstanziierungen zur Kompilierungszeit durchgeführt werden, ist auch hier keine Laufzeitunterstützung erforderlich.
    5. Dieser Prozess wird in letzter Zeit als Monomorphisierung bezeichnet , insbesondere in der Rust-Community. Das Wort wird im Gegensatz zum parametrischen Polymorphismus verwendet , der der Name des Konzepts ist, aus dem Generika stammen.
Theodoros Chatzigiannakis
quelle
7
Großartiger Vergleich mit C ++ - Vorlagen ... sie scheinen irgendwo zwischen den Generika von C # und Java zu liegen. Sie haben unterschiedlichen Code und unterschiedliche Strukturen für die Behandlung verschiedener spezifischer generischer Typen wie in C #, aber alles erfolgt in Kompilierungszeit wie in Java.
Luaan
3
In C ++ ermöglicht dies auch die Einführung einer Vorlagenspezialisierung, bei der jeder (oder nur einige) konkrete Typen unterschiedliche Implementierungen haben können. Offensichtlich nicht in Java möglich, aber auch nicht in C #.
Quetzalcoatl
@quetzalcoatl Ein Grund dafür ist jedoch, die Menge des produzierten Codes mit Zeigertypen zu reduzieren, und C # macht etwas, das mit Referenztypen hinter den Kulissen vergleichbar ist. Dies ist jedoch nur ein Grund dafür, und es gibt definitiv Zeiten, in denen die Spezialisierung von Vorlagen hilfreich wäre.
Jon Hanna
27

Reifizierung bedeutet im Allgemeinen (außerhalb der Informatik) "etwas Reales machen".

Bei der Programmierung wird etwas geändert, wenn wir in der Sprache selbst auf Informationen darüber zugreifen können.

Nehmen wir für zwei vollständig nicht generikabezogene Beispiele für etwas, das C # tut und nicht geändert hat, Methoden und Speicherzugriff.

OO-Sprachen haben im Allgemeinen Methoden (und viele, die keine ähnlichen Funktionen haben , jedoch nicht an eine Klasse gebunden sind). Als solches können Sie eine Methode in einer solchen Sprache definieren, aufrufen, möglicherweise überschreiben und so weiter. Nicht in all diesen Sprachen können Sie die Methode selbst als Daten für ein Programm behandeln. Mit C # (und wirklich mit .NET anstelle von C #) können Sie MethodInfoObjekte verwenden, die die Methoden darstellen, sodass in C # Methoden angepasst werden. Methoden in C # sind "erstklassige Objekte".

Alle praktischen Sprachen haben einige Mittel, um auf den Speicher eines Computers zuzugreifen. In einer einfachen Sprache wie C können wir uns direkt mit der Zuordnung zwischen numerischen Adressen befassen, die vom Computer verwendet werden. Dies int* ptr = (int*) 0xA000000; *ptr = 42;ist also sinnvoll (solange wir einen guten Grund haben zu vermuten, dass der Zugriff auf die Speicheradresse 0xA000000auf diese Weise gewonnen hat. t etwas in die Luft jagen). In C # ist dies nicht sinnvoll (wir können es in .NET fast erzwingen, aber mit der .NET-Speicherverwaltung ist es nicht sehr wahrscheinlich, dass Dinge verschoben werden). C # hat keine verifizierten Speicheradressen.

So, wie refied Mittel „made real“ ein „verdinglicht Typ“ ist eine Art können wir „Rede über“ in der Sprache , in Frage.

Bei Generika bedeutet dies zwei Dinge.

Eines ist, dass List<string>es sich um einen Typ handelt, der genauso ist stringoder intist. Wir können diesen Typ vergleichen, seinen Namen erhalten und uns danach erkundigen:

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

Dies hat zur Folge, dass wir über die Parametertypen einer generischen Methode (oder einer Methode einer generischen Klasse) innerhalb der Methode selbst "sprechen" können:

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
}

Zu viel zu tun ist in der Regel "stinkend", hat aber viele nützliche Fälle. Schauen Sie sich zum Beispiel an:

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

Dies führt nicht viele Vergleiche zwischen dem Typ TSourceund verschiedenen Typen für unterschiedliche Verhaltensweisen durch (im Allgemeinen ein Zeichen dafür, dass Sie überhaupt keine Generika hätten verwenden sollen), aber es wird zwischen einem Codepfad für Typen aufgeteilt, die sein können null(sollte nullif zurückgeben Kein Element gefunden und darf keine Vergleiche anstellen, um das Minimum zu finden, wenn eines der verglichenen Elemente ist null) und den Codepfad für Typen, die nicht gefunden werden können null(sollte geworfen werden, wenn kein Element gefunden wurde, und muss sich nicht um die Möglichkeit von nullElementen kümmern ).

Da dies TSourceinnerhalb der Methode "real" ist, kann dieser Vergleich entweder zur Laufzeit oder zur Jitting-Zeit durchgeführt werden (im Allgemeinen Jitting-Zeit, sicherlich würde der obige Fall dies zur Jitting-Zeit tun und keinen Maschinencode für den nicht genommenen Pfad erzeugen), und wir haben a separate "echte" Version der Methode für jeden Fall. (Als Optimierung wird der Maschinencode für verschiedene Methoden für verschiedene Parameter vom Typ Referenztyp gemeinsam genutzt, da dies nicht beeinflusst werden kann und wir daher die Menge des ausgestoßenen Maschinencodes reduzieren können.)

(Es ist nicht üblich , über Verdinglichung von generischen Typen in C # zu sprechen , wenn Sie auch mit Java beschäftigen, denn in C # wir diese Verdinglichung nehmen nur für selbstverständlich, alle Arten verdinglicht werden in Java, nicht-generischen Typen werden als. Verdinglicht , weil das ist eine Unterscheidung zwischen ihnen und generischen Typen).

Jon Hanna
quelle
Sie halten es nicht für Minsinnvoll, das zu tun, was oben sinnvoll ist? Es ist sehr schwer, sein dokumentiertes Verhalten anders zu erfüllen.
Jon Hanna
Ich betrachte den Fehler als das (nicht) dokumentierte Verhalten und die Implikation, dass dieses Verhalten nützlich ist (abgesehen davon unterscheidet sich das Verhalten von Enumerable.Min<TSource>darin, dass es nicht für Nichtreferenztypen in eine leere Sammlung wirft, sondern Standard zurückgibt (TSource) und wird nur als "Gibt den Mindestwert in einer generischen Sequenz zurück" dokumentiert. Ich würde argumentieren, dass beide auf eine leere Sammlung werfen sollten oder dass ein "Null" -Element als Basislinie übergeben werden sollte, und der Komparator / Vergleichsfunktion sollte immer übergeben werden)
Martijn
1
Das wäre viel weniger nützlich als das aktuelle Min, das dem allgemeinen Datenbankverhalten bei nullbaren Typen entspricht, ohne das Unmögliche bei nicht nullbaren Typen zu versuchen. (Die Grundidee ist nicht unmöglich, aber nicht sehr nützlich, es sei denn, es gibt einen Wert, von dem Sie wissen, dass er niemals in der Quelle enthalten sein würde.)
Jon Hanna
1
Thingification wäre dafür ein besserer Name gewesen. :)
Tchrist
@tchrist eine Sache kann unwirklich sein.
Jon Hanna
15

Wie Duffymo bereits bemerkte , ist "Verdinglichung" nicht der Hauptunterschied.

In Java dienen Generika im Wesentlichen dazu, die Unterstützung zur Kompilierungszeit zu verbessern. Sie können stark typisierte, z. B. Sammlungen in Ihrem Code verwenden und die Typensicherheit für Sie erledigen. Dies existiert jedoch nur zur Kompilierungszeit - der kompilierte Bytecode hat keine Vorstellung von Generika mehr; Alle generischen Typen werden in "konkrete" Typen umgewandelt ( objectwenn der generische Typ unbegrenzt ist), wobei nach Bedarf Typkonvertierungen und Typprüfungen hinzugefügt werden.

In .NET sind Generika ein wesentliches Merkmal der CLR. Wenn Sie einen generischen Typ kompilieren, bleibt dieser in der generierten IL generisch. Es wird nicht nur wie in Java in nicht generischen Code umgewandelt.

Dies hat verschiedene Auswirkungen auf die Funktionsweise von Generika in der Praxis. Beispielsweise:

  • Java muss SomeType<?>es Ihnen ermöglichen, jede konkrete Implementierung eines bestimmten generischen Typs zu übergeben. C # kann dies nicht - jeder spezifische ( reifizierte ) generische Typ ist ein eigener Typ.
  • Ungebundene generische Typen in Java bedeuten, dass ihr Wert als gespeichert wird object. Dies kann sich auf die Leistung auswirken, wenn Werttypen in solchen Generika verwendet werden. Wenn Sie in C # einen Werttyp in einem generischen Typ verwenden, bleibt dieser ein Werttyp.

Nehmen wir an, Sie haben einen Listgenerischen Typ mit einem generischen Argument, um ein Beispiel zu geben . In Java List<String>und List<Int>wird zur Laufzeit genau derselbe Typ sein - die generischen Typen existieren nur für Code zur Kompilierungszeit. Alle Anrufe zB GetValuewird transformiert werden (String)GetValueund (Int)GetValuejeweils.

In C # List<string>und List<int>sind zwei verschiedene Typen. Sie sind nicht austauschbar und ihre Typensicherheit wird auch zur Laufzeit erzwungen. Egal was Sie tun, es new List<int>().Add("SomeString")wird niemals funktionieren - der zugrunde liegende Speicher in List<int>ist wirklich ein ganzzahliges Array, während es in Java notwendigerweise ein objectArray ist. In C # gibt es keine Casts, kein Boxen usw.

Dies sollte auch deutlich machen, warum C # nicht dasselbe tun kann wie Java SomeType<?>. In Java sind alle generischen Typen, von denen "abgeleitet" SomeType<?>wird, genau der gleiche Typ. In C # sind alle verschiedenen spezifischen SomeType<T>s ihre eigenen separaten Typen. Wenn Sie die Überprüfungen zur Kompilierungszeit entfernen, ist es möglich, SomeType<Int>stattdessen zu bestehen SomeType<String>(und das SomeType<?>bedeutet nur, dass Sie die Überprüfungen zur Kompilierungszeit für den angegebenen generischen Typ ignorieren). In C # ist dies nicht möglich, auch nicht für abgeleitete Typen (das heißt, Sie können dies nicht tun List<object> list = (List<object>)new List<string>();, obwohl stringes von abgeleitet ist object).

Beide Implementierungen haben ihre Vor- und Nachteile. Es gab einige Male, in denen ich es geliebt hätte, nur SomeType<?>als Argument in C # zuzulassen - aber es macht einfach keinen Sinn, wie C # -Generika funktionieren.

Luaan
quelle
2
Nun, können Sie nutzen die Typen machen List<>, Dictionary<,>und so weiter in C #, aber der Abstand zwischen diesem und einem bestimmten konkreten Liste oder Wörterbuch nimmt ziemlich viel Reflexion Brücke. Abweichungen bei den Schnittstellen helfen in einigen Fällen, in denen wir diese Lücke vielleicht einmal leicht überbrücken wollten, aber nicht bei allen.
Jon Hanna
2
@JonHanna Sie können List<>damit einen neuen bestimmten generischen Typ instanziieren - es bedeutet jedoch immer noch, den gewünschten bestimmten Typ zu erstellen. Aber Sie können List<>zum Beispiel nicht als Argument verwenden. Aber ja, zumindest können Sie so die Lücke durch Reflexion schließen.
Luaan
Das .NET Framework verfügt über drei fest codierte allgemeine Einschränkungen, die keine Speicherorttypen sind. Alle anderen Einschränkungen müssen Speicherorttypen sein. Ferner werden die Zeiten nur ein generischer Typ Tkann eine lagerortspezifische Typ Bedingung erfüllen Usind , wenn Tund Uvom gleichen Typ sind , oder Uist ein Typ, der einen Verweis auf eine Instanz halten kann T. Es wäre nicht möglich, einen Speicherort vom Typ sinnvoll zu haben, SomeType<?>aber theoretisch wäre es möglich, eine generische Einschränkung dieses Typs zu haben.
Supercat
1
Es ist nicht wahr, dass kompilierter Java-Bytecode keine Vorstellung von Generika hat. Es ist nur so , dass die Klasse Instanzen keine Ahnung von Generika haben. Dies ist ein wichtiger Unterschied; Ich habe zuvor unter programmers.stackexchange.com/questions/280169/… darüber geschrieben , wenn Sie interessiert sind.
Ruakh
2

Reification ist ein objektorientiertes Modellierungskonzept.

Reify ist ein Verb, das bedeutet "etwas Abstraktes real werden lassen" .

Wenn Sie objektorientiert programmieren, ist es üblich, reale Objekte als Softwarekomponenten zu modellieren (z. B. Fenster, Schaltfläche, Person, Bank, Fahrzeug usw.).

Es ist auch üblich, abstrakte Konzepte in Komponenten umzuwandeln (z. B. WindowListener, Broker usw.).

Duffymo
quelle
2
Reification ist ein allgemeines Konzept für "etwas Reales machen", das zwar für die objektorientierte Modellierung gilt, aber auch im Kontext der Implementierung von Generika eine Bedeutung hat.
Jon Hanna
2
Ich bin durch das Lesen dieser Antworten erzogen worden. Ich werde meine Antwort ändern.
Duffymo
2
Diese Antwort spricht nicht das Interesse des OP an Generika und parametrischem Polymorphismus an.
Erick G. Hagstrom
Dieser Kommentar trägt nicht dazu bei, das Interesse anderer anzusprechen oder Ihren Repräsentanten zu stärken. Ich sehe, du hast überhaupt nichts angeboten. Meine war die erste Antwort, und sie definierte die Verdinglichung als etwas Weiteres.
Duffymo
1
Ihre Antwort war vielleicht die erste, aber Sie haben eine andere Frage beantwortet, nicht die vom OP gestellte, die sich aus dem Inhalt der Frage und ihren Tags ergeben hätte. Vielleicht haben Sie die Frage nicht gründlich gelesen, bevor Sie Ihre Antwort geschrieben haben, oder vielleicht wussten Sie nicht, dass der Begriff "Verdinglichung" im Kontext von Generika eine etablierte Bedeutung hat. In jedem Fall ist Ihre Antwort nicht hilfreich. Downvote.
jcsahnwaldt Reinstate Monica