2017-07-28 7 views
2

auf diese Frage Referenzierung: Cannot create an instance of the variable type 'Item' because it does not have the new() constraintinitialisieren generischer Typ mit nicht-leeren Konstruktor

Ich mag eine Instanz von einem generischen Typ mit einem nicht leeren Konstruktor erstellen.

public class A{}; 

public class B 
{ 
    public B(int){/*...*/} 
} 

public class C 
{ 
    public static T validate<T>(int index) 
     where T : A, 
     new(int) //error 
    { 
     if(/*validation*/) 
     return null; 
     return new T(index); 
    } 
} 

Wenn ich versuche, B b = validate<B>(42); zu anrufen habe ich diesen Fehler:

'B' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method 'C.validate(int)'

Frage 1: Warum kann ich keine Parameter Konstrukteure nur?

Frage 2: Wie kann ich mein Problem ohne Delegaten oder Schnittstellen lösen? (Ich bin immer auf der Suche nach ausgefallenen Lösungen.)

+1

Die Antwort auf Frage 1 ist einfach: [es ist noch nicht gebaut] (https://github.com/dotnet/roslyn/issues/2206).Dies ist keine C# -Begrenzung; Die CLR selbst unterstützt solche Einschränkungen nicht und müsste erweitert werden, um dies zu ermöglichen. Es ist nicht eines dieser Features, die so toll sind, dass die Leute an der Spitze plappern, um es hineinzulegen. (Stellen Sie sich zunächst 'new (int, int, bool, int, bool)' vor - es könnte die Form eines Ihrer haben Konstruktoren, aber viel Glück herauszufinden, was dieser Konstruktor * tun * soll.) –

+0

Eine einfache Lösung wäre, die 'new()' - Einschränkung zu verwenden und den Standardkonstruktor zu verwenden (fügen Sie ihn hinzu). Dann nutzen Sie die 'Index' Eigenschaft:' public static T Validate (int index) where T: A, new() { if (Validierung) return null; T t = neu T(); t.Index = Index; Rückkehr t; } 'Sonst könnten Sie' Activator.CreateInstance' wie [hier] verwenden (https://Stackoverflow.com/a/15003163/284240) –

+0

Gute Nachrichten - sie wollen es nicht bauen. Siehe diesen Beitrag: https://github.com/dotnet/csharplang/issues/769. Dieser Beitrag enthält auch eine mögliche Lösung/Abhilfe. – Mafii

Antwort

3

Sie Reflexion den Konstruktor aufrufen können zu erstellen:

var constructorInfo = typeof(T).GetConstructor(new[] { typeof(int) }); 
if (constructorInfo != null) 
{ 
    object[] parameters = new object[] { index }; 
    return (T)constructorInfo.Invoke(parameters); 
} 
else 
{ 
    // handle it 
} 

(angepasst von https://stackoverflow.com/a/13523659/5962841)


Oder können Sie Verwenden Sie den Aktivator, um eine Instanz zu erstellen (siehe Antwort von @ datrax)

(T)Activator.CreateInstance(typeof(T), index) 

Die Funktion, nach der Sie gefragt haben, wurde bereits oft angefordert.

Die Anfrage für das Feature wird hier verfolgt (github csharp design repo), aber erwarte es nicht in diesem Jahr, es ist nicht einmal Prototyp oder akzeptiert.

1

Sie dürfen kein Argument an den Konstruktor des generischen Typs übergeben. Die einzige mögliche Einschränkung ist: wo T: new(). Aber trotzdem ist es eine Möglichkeit, noch eine solche Instanz

(T)Activator.CreateInstance(typeof(T), index); 
2

Das Problem hier eignet sich nicht wirklich für C# Generika.

Eine allgemeine Art, die eine gemeinsame Schnittstelle, soweit Eigenschaften & Methoden haben sollte, das ist der springende Punkt bei T spezifiziert

Hier eine Instanz von A oder eine Unterklasse von A sein muss, A nicht über eine int-Konstruktor, aber Sie möchten eine Instanz einer Unterklasse erstellen, die einen int-Konstruktor hat.

Auch von Ihnen Code, B erbt nicht von A, aber Sie haben T: A in Ihrer generischen Methode angegeben. Ist das nur ein Versehen?

Die Verwendung von Activator hängt auch von der späten Bindung ab. Wenn Sie sie also für eine Klasse aufrufen, die keinen geeigneten Konstruktor hat, erhalten Sie einen Laufzeitfehler.

Ein funktioneller Ansatz ist intuitiver, sieht sauberer aus und ist nicht auf Laufzeitreflexion angewiesen.

public class A { 
}; 

public class B:A 
{ 
    public B(int y){ x = y; } 
    public int x { get; } 
} 

public class C 
{ 
    public static T validate<T>(int index, Func<int, T> instantiator) 
     where T : A 
    { 
     if (false) 
      return null; 

     return instantiator(index); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     B b = C.validate<B>(42, (y)=>new B(y)); 
    } 
} 
+0

Absolut. Btw: Ihr 'instanziator' ist auch als Factory-Methode bekannt und sollte wahrscheinlich' factory' heißen, wie es die meisten Programmierer tun. – Mafii

+0

@Mafii richtig, aber ich benannte den Parameter auf diese Weise, um die Benennung von Activator zu spiegeln und das Muster ein wenig offensichtlicher zu machen. –

+0

Fair genug, macht Sinn, I + 1, da es die sauberste/einzige Kompilierzeit sichere Lösung ist. – Mafii