2017-02-14 1 views
0

Ich versuche, Generics mit Einschränkungen zu verwenden, die scheinbar nicht unterstützt werden, und ich frage mich, ob es einen sauberen Workaround gibt.Generics mit Typabhängigkeiten basierend auf statischen Membern

Das Problem mit meinem ersten Versuch hängt von der Tatsache ab, dass Sie keine statischen Member in Schnittstellen haben können und daher können Sie keine Schnittstellen verwenden, um Typen in generischen Deklarationen basierend auf statischen Membern einzuschränken.

dieses Beispiel vor:

Angenommen, Sie in einem allgemeinen Kontext arbeiten, aber Sie wollen sicher sein, dass Fälle von T der Lage sind, sich in einen Stream serialisiert werden (könnte eine Datei sein, eine Liste von Bytes) und dass Instanzen von T aus einem Stream deserialisiert werden können.

Im Fall des Schreibens ist dies relativ einfach, weil es mit, dass Sie bereits eine Instanz zu arbeiten (wenn Sie nicht in der Lage sein werden, serialisiert null) angenommen werden kann: in dem

public interface IStreamWritable 
{ 
    // When called, this IStreamWritable instance 
    // serializes itself to a stream. 
    void Write(IStream stream); 
} 

... 

void Write<T>(IStream stream, T t) where T : IStreamWritable 
{ 
    t.Write(stream); 
} 

jedoch Fall des Lesens, in denen Sie ein Problem:

public interface IStreamReadable 
{ 
    // When called, returns an instance 
    // deserialized from the stream. 
    void Read(IStream stream); 
} 

... 

T Read<T>(IStream stream) where T : IStreamReadable, new() 
{ 
    var t = new T(); 
    t.Read(stream); 
    return t; 
} 

Dies könnte zur Arbeit erscheinen, aber es macht Annahmen darüber, wie das Objekt wird deserialisiert instanziiert werden. Vielleicht möchten Sie eine vorhandene Instanz zurückgeben anstatt eine neue Instanz zu erstellen? Es erfordert auch die new() Einschränkung, die unerwünscht sein kann.

Wenn Sie außerhalb des Kontextes einer bestimmten Instanz arbeiten, ist es sinnvoll, stattdessen in einem statischen Kontext zu arbeiten. So könnte man dies versuchen:

public interface IStreamReadable 
{ 
    // When called, returns an instance 
    // deserialized from the stream. 
    static IStreamReadable Read(IStream stream); 
} 

... 

T Read(IStream stream) where T : IStreamReadable 
{ 
    return T.Read(stream); 
} 

Oder alternativ zu vermeiden Boxen:

public interface IStreamReadable<T> where T : IStreamReadable<T> 
{ 
    // When called, returns an instance 
    // deserialized from the stream. 
    static T Read(IStream stream); 
} 

... 

T Read(IStream stream) where T : IStreamReadable<T> 
{ 
    return T.Read(stream); 
} 

Leider weder compiliert, weil Sie keine statischen Elemente in Schnittstellen deklarieren können. Wenn der Compiler dies jedoch tun würde, wäre dies die ideale Lösung, da er keine Annahmen darüber macht, wie Sie Instanziierung handhaben, und stattdessen diese Verantwortung an den Schnittstellenimplementierer delegiert.

fand ich eine etwas schöne Lösung, die im Fall von structs funktioniert:

public interface IFoo 
{ 
    void Foo(); 
} 

... 

CallFoo<T>() where T : struct, IFoo 
{ 
    return default(T).Foo(); 
} 

Wo die Implementierer von IFoo eine statische Methode aufruft. Natürlich wird dieser Ansatz bei Referenztypen aufgrund von default(T), die in diesem Fall null zurückgeben, fehlschlagen. Verwendung von return new T().Foo(); könnte auch funktionieren, aber dies erfordert die new() Einschränkung erneut, und verwirft die Instanz T unnötig Müll zu erstellen.

Ich habe überlegt, Reflexion irgendwie als eine Arbeit zu verwenden, aber ich fragte mich, ob jemand ihre eigene Arbeit um diese Einschränkung, die sie teilen möchten, gekommen ist.

+0

Der Gedanke, dass eine Klasse für das Serialisieren/Deserialisieren von Instanzen verantwortlich sein sollte, verstößt gegen das Prinzip der einfachen Verantwortlichkeit. Warum trennen Sie die Lese-/Schreibmethoden nicht in eine separate Klasse/Schnittstelle? – StriplingWarrior

+0

Während Sie einen guten Punkt haben können, war es als ein Beispiel und nicht das spezifische Problem gemeint. Tolles Thema für eine weitere Diskussion, sicher. – Hatchling

+0

Ich würde vermuten, dass, wenn dieses Beispiel das Problem darstellt, dem Sie gegenüberstehen, das gleiche Prinzip wahrscheinlich auch für Ihr Problem in der realen Welt gilt. – StriplingWarrior

Antwort

1

Ihr Problem gelöst werden, wenn Sie die Verantwortung für die Schaffung IStreamReadable Objekte zu einer „Factory“ Klasse delegieren

using System; 

namespace ConsoleApplication5 
{ 
    public interface IStream { } 
    public interface IStreamWritable { void Write(IStream stream); } 
    public interface IStreamReadable { void Read(IStream stream); } 
    public interface IStreamReadableFactory { IStreamReadable Create(); } 

    public class InstanceFactory : IStreamReadableFactory 
    { 
     public IStreamReadable Create() { throw new NotImplementedException(); } 
    } 
    public class StaticFactory : IStreamReadableFactory 
    { 
     public static IStreamReadable GetInstance() { throw new NotImplementedException(); } 
     IStreamReadable IStreamReadableFactory.Create() { return GetInstance(); } 
    } 

    public static class Program 
    { 
     public static void Main() 
     { 
      IStream stream = null; 
      var s1 = Read(new StaticFactory(), stream); 
      var s2 = Read(new InstanceFactory(), stream); 
     } 

     static IStreamReadable Read(IStreamReadableFactory factory, IStream stream) 
     { 
      var t = factory.Create(); 
      t.Read(stream); 
      return t; 
     } 
    } 
} 
1

Angenommen, Sie in einem allgemeinen Kontext arbeiten, aber Sie wollen sicher sein, dass Instanzen von T sind in der Lage, sich in einen Stream zu serialisieren (könnte eine Datei, eine Liste von Bytes sein), und dass Instanzen von T aus einem Stream deserialisiert werden können.

Ich persönlich würde nicht egal, ob Instanzen von Tdeserialisieren können sich, so viel wie, dass sie können auf/aus einem Stream serialisiert werden. Der Weg dazu besteht darin, T nicht zu erzwingen, eine Implementierung für diese Methoden bereitzustellen (da diese Klasse wahrscheinlich andere Verantwortlichkeiten hat), sondern jemanden zu zwingen, eine Implementierung bereitzustellen, die kann.

die Schnittstelle Gegeben:

public T GetFromFile<T>(string path, IStreamDeserializer<T> deserializer) 
{ 
    using (var stream = GetFileStream(path)) 
    { 
     return deserializer.Read(stream); 
    } 
} 

Um also GetFromFile<Foo>(...) zu nennen, müsste produzieren jemand eine Klasse, die weiß, wie:

public interface IStreamDeserializer<T> 
{ 
    T Read(IStream stream); 
} 

... Sie eine Methode so schreiben könnte Deserialisieren Foo Objekte. Sie wären injizieren diese Abhängigkeit in Ihre Methode.

Natürlich ist die Existenz eines Deserializer kann keine Voraussetzung von sein jeder Implementierung von GetFromFile() --Dieser ein Aspekt der Implementierung ist, die aus verschiedenen Gründen als Ihre Methodensignatur ändern können. Sie sollten also wahrscheinlich Konstruktor Injektion verwenden, was bedeuten würde, dass Ihre Klasse generisch wird und nicht nur Ihre Methode.

public class FileEntityRetriever<T> : IFileEntityRetriever 
{ 
    IStreamDeserializer<T> deserializer; 

    public FileEntityRetriever(IStreamDeserializer<T> deserializer) 
    { 
     this.deserializer = deserializer; 
    } 

    public T GetFromFile(string path, IStreamDeserializer<T> deserializer) 
    { 
     using (var stream = GetFileStream(path)) 
     { 
      return deserializer.Read(stream); 
     } 
    } 

} 
Verwandte Themen