it-swarm.com.de

Instanz des generischen Typs erstellen?

Wenn BaseFruit einen Konstruktor hat, der einen int weight akzeptiert, kann ich dann ein Stück Obst in einer generischen Methode wie dieser instanziieren?

public void AddFruit<T>()where T: BaseFruit{
    BaseFruit fruit = new T(weight); /*new Apple(150);*/
    fruit.Enlist(fruitManager);
}

Ein Beispiel wird hinter Kommentaren hinzugefügt. Es scheint, dass ich dies nur tun kann, wenn ich BaseFruit einen parameterlosen Konstruktor gebe und dann alles durch Membervariablen fülle. In meinem echten Code (nicht über Obst) ist das eher unpraktisch.

-Update-
Es scheint also, dass es in keiner Weise durch Einschränkungen gelöst werden kann. Aus den Antworten ergeben sich drei Lösungsvorschläge:

  • Fabrikmuster
  • Reflexion
  • Aktivator

Ich neige dazu zu denken, dass das Nachdenken am wenigsten sauber ist, aber ich kann mich nicht zwischen den beiden anderen entscheiden.

194
Boris Callens

Zusätzlich ein einfacheres Beispiel:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight });

Beachten Sie, dass die Verwendung der new () - Einschränkung für T nur dazu dient, dass der Compiler zur Kompilierzeit nach einem öffentlichen parameterlosen Konstruktor sucht. Der eigentliche Code zum Erstellen des Typs ist die Activator-Klasse.

Sie müssen sich bezüglich des spezifischen Konstruktors selbst vergewissern, und diese Art von Anforderung kann ein Codegeruch sein (oder eher etwas, das Sie in der aktuellen Version von c # vermeiden sollten).

284
meandmycode

Sie können keinen parametrisierten Konstruktor verwenden. Sie können einen parameterlosen Konstruktor verwenden, wenn Sie eine Einschränkung "where T : new()" haben.

Es ist ein Schmerz, aber so ist das Leben :(

Dies ist eines der Dinge, die ich mit "statische Schnittstellen" ansprechen möchte. Sie können dann T zwingen, statische Methoden, Operatoren und Konstruktoren einzuschließen, und diese dann aufrufen.

83
Jon Skeet

Ja; Ändern Sie Ihre Position:

where T:BaseFruit, new()

Dies funktioniert jedoch nur mit parameterless -Konstruktoren. Sie müssen andere Möglichkeiten haben, um Ihre Eigenschaft festzulegen (Einstellung der Eigenschaft selbst oder ähnliches).

48
Adam Robinson

Einfachste Lösung Activator.CreateInstance<T>()

21
user1471935

Wie Jon darauf hinwies, ist dies das Leben, weil er einen nicht-parameterlosen Konstruktor zwingt. Eine andere Lösung ist jedoch die Verwendung eines Fabrikmusters. Dies ist leicht zu beschränken 

interface IFruitFactory<T> where T : BaseFruit {
  T Create(int weight);
}

public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {    
  BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/    
  fruit.Enlist(fruitManager);
}

Eine weitere Option ist die Verwendung eines funktionalen Ansatzes. Übergeben Sie eine Werksmethode.

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
  BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
  fruit.Enlist(fruitManager);
}
16
JaredPar

Sie können dies mit Reflektion tun:

public void AddFruit<T>()where T: BaseFruit
{
  ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}

BEARBEITEN: Konstruktor hinzugefügt == Nullprüfung.

BEARBEITEN: Eine schnellere Variante, die einen Cache verwendet:

public void AddFruit<T>()where T: BaseFruit
{
  var constructor = FruitCompany<T>.constructor;
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}
private static class FruitCompany<T>
{
  public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
}
11
mmmmmmmm

Ich habe diese Methode erstellt:

public static V ConvertParentObjToChildObj<T,V> (T obj) where V : new()
{
    Type typeT = typeof(T);
    PropertyInfo[] propertiesT = typeT.GetProperties();
    V newV = new V();
    foreach (var propT in propertiesT)
    {
        var nomePropT = propT.Name;
        var valuePropT = propT.GetValue(obj, null);

        Type typeV = typeof(V);
        PropertyInfo[] propertiesV = typeV.GetProperties();
        foreach (var propV in propertiesV)
        {
            var nomePropV = propV.Name;
            if(nomePropT == nomePropV)
            {
                propV.SetValue(newV, valuePropT);
                break;
            }
        }
    }
    return newV;
}

Ich benutze das auf diese Weise:

public class A 
{
    public int PROP1 {get; set;}
}

public class B : A
{
    public int PROP2 {get; set;}
}

Code:

A instanceA = new A();
instanceA.PROP1 = 1;

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);

Als Ergänzung zum Vorschlag von user1471935: 

Um eine generische Klasse mithilfe eines Konstruktors mit einem oder mehreren Parametern zu instanziieren, können Sie jetzt die Activator-Klasse verwenden.

T instance = Activator.CreateInstance(typeof(T), new object[] {...}) 

Die Liste der Objekte sind die Parameter, die Sie angeben möchten. Laut Microsoft :

CreateInstance erstellt eine Instanz des angegebenen Typs unter Verwendung des Konstruktors, der den angegebenen Parametern am besten entspricht.

Es gibt auch eine generische Version von CreateInstance (CreateInstance<T>()), in der Sie jedoch keine Konstruktorparameter angeben können. 

0
Rob Vermeulen

Vor kurzem bin ich auf ein sehr ähnliches Problem gestoßen. Ich wollte nur unsere Lösung mit Ihnen teilen. Ich wollte eine Instanz eines Car<CarA> aus einem Json-Objekt erstellen, mit dem eine Aufzählung erstellt wurde:

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>();

mapper.Add(1, typeof(CarA));
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class
{       
    public T Detail { get; set; }
    public Car(T data)
    {
       Detail = data;
    }
}
public class CarA
{  
    public int PropA { get; set; }
    public CarA(){}
}
public class CarB
{
    public int PropB { get; set; }
    public CarB(){}
}

var jsonObj = {"Type":"1","PropA":"10"}
MyEnum t = GetTypeOfCar(jsonObj);
Type objectT = mapper[t]
Type genericType = typeof(Car<>);
Type carTypeWithGenerics = genericType.MakeGenericType(objectT);
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) });
0
farshid