2009-03-02 6 views
66

Wie würden Sie Spezialisierung in C# machen? Ich werde ein Problem darstellen. Sie haben einen Vorlagentyp, Sie haben keine Ahnung, was es ist. Aber Sie wissen, ob es von XYZ abgeleitet ist, das Sie aufrufen möchten .alternativeFunc(). Eine gute Möglichkeit besteht darin, eine spezialisierte Funktion oder Klasse aufzurufen und normalCall return.normalFunc() zurückzugeben, während die andere Spezialisierung für jeden abgeleiteten Typ von XYZ den Aufruf .alternativeFunc() hat. Wie würde dies in C# geschehen?Wie Template-Spezialisierung in C tun #

+1

Ich verstehe die Frage nicht. Ist das keine normale Vererbung? –

Antwort

53

vorausgesetzt, Sie sprechen über Template-Spezialisierung, wie es mit C++ - Vorlagen getan werden kann - eine Funktion wie diese ist in C# nicht wirklich verfügbar. Dies liegt daran, dass C# -Generika nicht während der Kompilierung verarbeitet werden und eher ein Merkmal der Laufzeit sind.

Sie können jedoch ähnliche Wirkung mit C# 3.0-Erweiterungsmethoden erzielen. Hier ist ein Beispiel, das zeigt, wie man die Erweiterungsmethode nur für den Typ "MyClass" hinzufügt, was genau wie die Template-Spezialisierung ist. Beachten Sie jedoch, dass Sie nicht diese Standardimplementierung der Methode verstecken können, weil C# Compiler immer Standardverfahren zur Erweiterung Methoden bevorzugt:

class MyClass<T> { 
    public int Foo { get { return 10; } } 
} 
static class MyClassSpecialization { 
    public static void Bar(this MyClass<int> cls) { 
    return cls.Foo + 20; 
    } 
} 

Jetzt können Sie schreiben:

var cls = new MyClass<int>(); 
cls.Bar(); 

Wenn Sie wollen einen Standardfall für die Methode, die verwendet werden würde, wenn keine Spezialisierung zur Verfügung gestellt, als ich glaube, eine generische „Bar“ Erweiterungsmethode schreiben sollte es tun:

public static void Bar<T>(this MyClass<T> cls) { 
    return cls.Foo + 42; 
    } 
+0

Foo Eigenschaft vs Bar-Methode ... scheint nicht wirklich wie eine typische Spezialisierung ... –

+0

Das ist eine großartige Lösung, +1 –

+1

Nein, es ist nicht typische Spezialisierung, aber es ist die einzige einfache Sache, die Sie tun können ... (AFAIK) –

0

Wenn Sie nur testen wollen, ob ein Typ von XYZ schließt man ist, dann können Sie verwenden:

theunknownobject.GetType().IsAssignableFrom(typeof(XYZ)); 

Wenn ja, können Sie „theunknownobject“ XYZ werfen und rufen alternativeFunc() wie folgt aus:

XYZ xyzObject = (XYZ)theunknownobject; 
xyzObject.alternativeFunc(); 

Hoffe das hilft.

+1

Ich weiß nicht viel C#, aber wer Sie gewählt hat, sollte gesagt warum. Ich habe keine Ahnung, was falsch ist, wenn Ihre Antwort oder wenn irgendetwas damit nicht stimmt. –

+0

Nicht sicher auch nicht. Es scheint mir ausreichend zu sein. Obwohl ein bisschen mehr als nötig. – jalf

+2

Es war nicht ich, aber es ist, weil die Antwort für die Frage völlig irrelevant ist. Lookup '" C++ Template-Spezialisierung "' – georgiosd

74

In C# ist die Spezialisierung, die der Spezialisierung am nächsten kommt, eine spezifischere Überladung; Dies ist jedoch spröde und deckt nicht jede mögliche Verwendung ab. Zum Beispiel:

void Foo<T>(T value) {Console.WriteLine("General method");} 
void Foo(Bar value) {Console.WriteLine("Specialized method");} 

Wenn hier der Compiler die Typen bei der Kompilierung kennt, wird es die meisten spezifischen wählen:

Bar bar = new Bar(); 
Foo(bar); // uses the specialized method 

aber ....

void Test<TSomething>(TSomething value) { 
    Foo(value); 
} 

wird verwenden Foo<T> auch für TSomething=Bar, da dies zur Kompilierzeit gebrannt wird.

Ein anderer Ansatz ist die Verwendung von Typprüfung innerhalb eine generische Methode - jedoch ist dies in der Regel eine schlechte Idee, und wird nicht empfohlen.

Grundsätzlich C# will einfach nicht, dass Sie mit Spezialisierungen arbeiten, mit Ausnahme von Polymorphismus:

class SomeBase { public virtual void Foo() {...}} 
class Bar : SomeBase { public override void Foo() {...}} 

Hier Bar.Foo wird immer auf die korrekte Überschreibung lösen.

13

Durch Hinzufügen einer Zwischenklasse und eines Wörterbuchs ist Spezialisierung möglich.

Um sich auf T zu spezialisieren, erstellen wir eine generische Schnittstelle mit einer Methode, die (z. B.) Apply genannt wird. Für die spezifischen Klassen, die diese Schnittstelle implementiert, muss die Methode Apply spezifisch für diese Klasse definiert werden. Diese Zwischenklasse wird als Traits-Klasse bezeichnet.

Diese Traits-Klasse kann als Parameter im Aufruf der generischen Methode angegeben werden, die dann (natürlich) immer die richtige Implementierung übernimmt.

Anstelle der manuellen Angabe kann die Merkmalsklasse auch in einem globalen IDictionary<System.Type, object> gespeichert werden. Es kann dann nachgeschlagen werden und voila, du hast dort echte Spezialisierung.

Wenn praktisch, können Sie es in einer Erweiterungsmethode verfügbar machen.

class MyClass<T> 
{ 
    public string Foo() { return "MyClass"; } 
} 

interface BaseTraits<T> 
{ 
    string Apply(T cls); 
} 

class IntTraits : BaseTraits<MyClass<int>> 
{ 
    public string Apply(MyClass<int> cls) 
    { 
     return cls.Foo() + " i"; 
    } 
} 

class DoubleTraits : BaseTraits<MyClass<double>> 
{ 
    public string Apply(MyClass<double> cls) 
    { 
     return cls.Foo() + " d"; 
    } 
} 

// Somewhere in a (static) class: 
public static IDictionary<Type, object> register; 
register = new Dictionary<Type, object>(); 
register[typeof(MyClass<int>)] = new IntTraits(); 
register[typeof(MyClass<double>)] = new DoubleTraits(); 

public static string Bar<T>(this T obj) 
{ 
    BaseTraits<T> traits = register[typeof(T)] as BaseTraits<T>; 
    return traits.Apply(obj); 
} 

var cls1 = new MyClass<int>(); 
var cls2 = new MyClass<double>(); 

string id = cls1.Bar(); 
string dd = cls2.Bar(); 

diesen link meinen letzten Blog anzeigen und die folgen ups für eine ausführliche Beschreibung und Beispiele.

+0

Dies ist das Factory Pattern und es ist ein anständiger Weg, um mit * einigen * der Unzulänglichkeiten von Generika umzugehen. – Yaur

+1

@Yaur Ich sehe für mich wie ein Dekorateurmuster aus. –

5

Einige der vorgeschlagenen Antworten verwenden den Laufzeittyp info: inhärent langsamer als kompilierungszeitgebundene Methodenaufrufe.

Compiler erzwingt Spezialisierung nicht so gut wie in C++.

Ich würde empfehlen, PostSharp für eine Möglichkeit zu suchen, Code nach dem üblichen Compiler zu injizieren, um einen ähnlichen Effekt wie C++ zu erreichen.

2

Ich suchte nach einem Muster, um die Template-Spezialisierung zu simulieren. Es gibt einige Ansätze, die unter Umständen funktionieren können. Wie wäre es jedoch mit dem Fall

static void Add<T>(T value1, T value2) 
{ 
    //add the 2 numeric values 
} 

Es wäre möglich, die Aktion mit Anweisungen z. if (typeof(T) == typeof(int)). Aber es gibt einen besseren Weg, mit dem Overhead eines einzelnen virtuellen Funktionsaufruf reale Vorlage Spezialisierung zu simulieren:

public interface IMath<T> 
{ 
    T Add(T value1, T value2); 
} 

public class Math<T> : IMath<T> 
{ 
    public static readonly IMath<T> P = Math.P as IMath<T> ?? new Math<T>(); 

    //default implementation 
    T IMath<T>.Add(T value1, T value2) 
    { 
     throw new NotSupportedException();  
    } 
} 

class Math : IMath<int>, IMath<double> 
{ 
    public static Math P = new Math(); 

    //specialized for int 
    int IMath<int>.Add(int value1, int value2) 
    { 
     return value1 + value2; 
    } 

    //specialized for double 
    double IMath<double>.Add(double value1, double value2) 
    { 
     return value1 + value2; 
    } 
} 

Jetzt können wir schreiben, ohne den Typen im Voraus kennen:

static T Add<T>(T value1, T value2) 
{ 
    return Math<T>.P.Add(value1, value2); 
} 

private static void Main(string[] args) 
{ 
    var result1 = Add(1, 2); 
    var result2 = Add(1.5, 2.5); 

    return; 
} 

Wenn die Spezialisierung sollte nicht nur für die implementierten Typen, sondern auch für abgeleitete Typen aufgerufen werden, man könnte einen In Parameter für die Schnittstelle verwenden. In diesem Fall können die Rückgabetypen der Methoden jedoch nicht mehr vom generischen Typ T sein.

0

Ich denke, es ist eine Möglichkeit, es mit .NET zu erreichen 4+ mit dynamischer Auflösung:

static class Converter<T> 
{ 
    public static string Convert(T data) 
    { 
     return Convert((dynamic)data); 
    } 

    private static string Convert(Int16 data) => $"Int16 {data}"; 
    private static string Convert(UInt16 data) => $"UInt16 {data}"; 
    private static string Convert(Int32 data) => $"Int32 {data}"; 
    private static string Convert(UInt32 data) => $"UInt32 {data}"; 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Console.WriteLine(Converter<Int16>.Convert(-1)); 
     Console.WriteLine(Converter<UInt16>.Convert(1)); 
     Console.WriteLine(Converter<Int32>.Convert(-1)); 
     Console.WriteLine(Converter<UInt32>.Convert(1)); 
    } 
} 

Ausgang:

Int16 -1 
UInt16 1 
Int32 -1 
UInt32 1 

, die zeigt, dass eine andere Implementierung für unterschiedliche Arten aufgerufen wird.