Przekazywanie argumentów do C# generic new () typu template

Próbuję utworzyć nowy obiekt typu T poprzez jego konstruktor podczas dodawania do listy.

Otrzymuję błąd kompilacji: komunikat o błędzie to:

'T' : nie można podać argumentów podczas tworzenia instancji zmiennej

Ale moje klasy mają argument konstruktora! Jak mogę to zrobić?
public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T(listItem)); // error here.
   } 
   ...
}
Author: Rup, 2009-05-08

13 answers

Aby utworzyć instancję typu ogólnego w funkcji, musisz ją ograniczyć za pomocą znacznika "new".

public static string GetAllItems<T>(...) where T : new()

To jednak zadziała tylko wtedy, gdy chcesz wywołać konstruktor, który nie ma parametrów. Nie tutaj. Zamiast tego będziesz musiał podać inny parametr, który pozwala na tworzenie obiektu na podstawie parametrów. Najprostsza jest funkcja.

public static string GetAllItems<T>(..., Func<ListItem,T> del) {
  ...
  List<T> tabListItems = new List<T>();
  foreach (ListItem listItem in listCollection) 
  {
    tabListItems.Add(del(listItem));
  }
  ...
}

Możesz to tak nazwać

GetAllItems<Foo>(..., l => new Foo(l));
 370
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-08 15:11:04

W. Net 3.5 i po użyciu klasy aktywatora:

(T)Activator.CreateInstance(typeof(T), args)
 298
Author: user287107,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-02 12:07:34

Ponieważ nikt nie zadał sobie trudu, aby umieścić odpowiedź "refleksji" (która osobiście uważam za najlepszą odpowiedź), oto idzie:

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       Type classType = typeof(T);
       ConstructorInfo classConstructor = classType.GetConstructor(new Type[] { listItem.GetType() });
       T classInstance = (T)classConstructor.Invoke(new object[] { listItem });

       tabListItems.Add(classInstance);
   } 
   ...
}

Edit: ta odpowiedź jest przestarzała z powodu aktywatora. NET 3.5.CreateInstance, jednak nadal jest przydatny w starszych wersjach. NET.

 50
Author: James Jones,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-27 03:45:07

Obiekt initializer

Jeśli twój konstruktor z parametrem nie robi nic poza ustawieniem właściwości, możesz to zrobić w C# 3 lub lepiej używając object initializer zamiast wywoływać konstruktor (co jest niemożliwe, jak już wspomniano):

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T() { YourPropertyName = listItem } ); // Now using object initializer
   } 
   ...
}

Używając tego, zawsze możesz umieścić dowolną logikę konstruktora w domyślnym (pustym) konstruktorze.

Aktywator.CreateInstance()

Alternatywnie, możesz zadzwonić Aktywator.CreateInstance () Jak Tak:

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
        object[] args = new object[] { listItem };
        tabListItems.Add((T)Activator.CreateInstance(typeof(T), args)); // Now using Activator.CreateInstance
   } 
   ...
}

Zwróć uwagę na aktywator.CreateInstance może mieć pewien poziom wydajności , którego możesz unikać, jeśli szybkość wykonania jest priorytetem, a inna opcja jest dostępna dla ciebie.

 26
Author: Tim Lehner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:19

To nie zadziała w twojej sytuacji. Możesz tylko określić ograniczenie, że ma on pusty konstruktor:

public static string GetAllItems<T>(...) where T: new()

Możesz użyć właściwości injection definiując ten interfejs:

public interface ITakesAListItem
{
   ListItem Item { set; }
}

Wtedy możesz zmienić swoją metodę na taką:

public static string GetAllItems<T>(...) where T : ITakesAListItem, new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T() { Item = listItem });
   } 
   ...
}

Inną alternatywą jest Metoda Func opisana przez JaredPar.

 16
Author: Garry Shutler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-08 15:18:02

Bardzo stare pytanie, ale nowa odpowiedź; -)

Wersja ekspresyjna: (myślę, że najszybsze i najczystsze rozwiązanie)

Jak Welly Tambunan powiedział, "możemy również użyć drzewa wyrażeń do zbudowania obiektu"

Spowoduje to wygenerowanie 'konstruktora' (funkcji) dla podanego typu/parametrów. Zwraca delegata i akceptuje typy parametrów jako tablicę obiektów.

Here it jest:

// this delegate is just, so you don't have to pass an object array. _(params)_
public delegate object ConstructorDelegate(params object[] args);

public static ConstructorDelegate CreateConstructor(Type type, params Type[] parameters)
{
    // Get the constructor info for these parameters
    var constructorInfo = type.GetConstructor(parameters);

    // define a object[] parameter
    var paramExpr = Expression.Parameter(typeof(Object[]));

    // To feed the constructor with the right parameters, we need to generate an array 
    // of parameters that will be read from the initialize object array argument.
    var constructorParameters = parameters.Select((paramType, index) =>
        // convert the object[index] to the right constructor parameter type.
        Expression.Convert(
            // read a value from the object[index]
            Expression.ArrayAccess(
                paramExpr,
                Expression.Constant(index)),
            paramType)).ToArray();

    // just call the constructor.
    var body = Expression.New(constructorInfo, constructorParameters);

    var constructor = Expression.Lambda<ConstructorDelegate>(body, paramExpr);
    return constructor.Compile();
}

Przykład MyClass:

public class MyClass
{
    public int TestInt { get; private set; }
    public string TestString { get; private set; }

    public MyClass(int testInt, string testString)
    {
        TestInt = testInt;
        TestString = testString;
    }
}

Użycie:

// you should cache this 'constructor'
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, "test message");

Tutaj wpisz opis obrazka


inny przykład: przekazywanie typów jako tablicy

var type = typeof(MyClass);
var args = new Type[] { typeof(int), typeof(string) };

// you should cache this 'constructor'
var myConstructor = CreateConstructor(type, args);

// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, "test message");

DebugView wyrażenia

.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
    .New TestExpressionConstructor.MainWindow+MyClass(
        (System.Int32)$var1[0],
        (System.String)$var1[1])
}

Jest to odpowiednik wygenerowanego kodu:

public object myConstructor(object[] var1)
{
    return new MyClass(
        (System.Int32)var1[0],
        (System.String)var1[1]);
}

Mały minus

Wszystkie parametry valuetypes są pudełkowe, gdy są przekazywane jak tablica obiektów.


Prosty test wydajności:

private void TestActivator()
{
    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = Activator.CreateInstance(typeof(MyClass), 10, "test message");
    }
    sw.Stop();
    Trace.WriteLine("Activator: " + sw.Elapsed);
}

private void TestReflection()
{
    var constructorInfo = typeof(MyClass).GetConstructor(new[] { typeof(int), typeof(string) });

    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = constructorInfo.Invoke(new object[] { 10, "test message" });
    }

    sw.Stop();
    Trace.WriteLine("Reflection: " + sw.Elapsed);
}

private void TestExpression()
{
    var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

    Stopwatch sw = Stopwatch.StartNew();

    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = myConstructor(10, "test message");
    }

    sw.Stop();
    Trace.WriteLine("Expression: " + sw.Elapsed);
}

TestActivator();
TestReflection();
TestExpression();

Wyniki:

Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696

Użycie Expressions jest +/- 8 razy szybsze niż wywołanie ConstructorInfo i +/- 20 razy szybsze niż użycie Activator

 12
Author: J. van Langen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-11 07:46:41

Musisz dodać where t: new (), aby poinformować kompilator, że t ma gwarancję dostarczenia domyślnego konstruktora.

public static string GetAllItems<T>(...) where T: new()
 7
Author: Richard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-08 15:06:56

Jeśli po prostu chcesz zainicjować pole lub właściwość z parametrem konstruktora, w C# >= 3 możesz to zrobić bardzo łatwiej:

public static string GetAllItems<T>(...) where T : InterfaceOrBaseClass, new() 
{ 
   ... 
   List<T> tabListItems = new List<T>(); 
   foreach (ListItem listItem in listCollection)  
   { 
       tabListItems.Add(new T{ BaseMemberItem = listItem }); // No error, BaseMemberItem owns to InterfaceOrBaseClass. 
   }  
   ... 
} 
To samo powiedział Garry Shutler, ale chciałbym dodać dodatkową notatkę.

Oczywiście można użyć sztuczki właściwości, aby zrobić więcej rzeczy niż tylko ustawienie wartości pola. Właściwość " set ()" może wywołać dowolne przetwarzanie potrzebne do skonfigurowania powiązanych z nią pól oraz wszelkie inne potrzeby samego obiektu, w tym sprawdzenie, czy pełny inicjalizacja ma nastąpić przed użyciem obiektu, symulując pełną kontrukcję (tak, jest to brzydkie obejście, ale pokonuje ograniczenie m$new ()).

Nie mogę być pewien, czy to planowana dziura, czy przypadkowy efekt uboczny, ale działa.

To bardzo zabawne, jak M$ people dodaje nowe funkcje do języka i wydaje się, że nie robi pełnej analizy skutków ubocznych. Cała ogólna rzecz jest tego dobrym dowodem...

 7
Author: fljx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-30 19:46:35

Zauważyłem, że otrzymuję błąd "nie mogę podać argumentów podczas tworzenia instancji parametru typu T", więc musiałem to zrobić:

var x = Activator.CreateInstance(typeof(T), args) as T;
 5
Author: chris31389,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-06 11:47:47

Jeśli masz dostęp do klasy, z której zamierzasz korzystać, możesz użyć tego podejścia, którego użyłem.

Utwórz interfejs, który ma alternatywnego twórcę:

public interface ICreatable1Param
{
    void PopulateInstance(object Param);
}

Stwórz swoje klasy z pustym kreatorem i zaimplementuj tę metodę:

public class MyClass : ICreatable1Param
{
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        //populate the class here
    }
}

Teraz używaj metod ogólnych:

public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
    //do stuff
    T newT = new T();
    T.PopulateInstance(Param);
}

Jeśli nie masz dostępu, zawiń klasę docelową:

public class MyClass : ICreatable1Param
{
    public WrappedClass WrappedInstance {get; private set; }
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        WrappedInstance = new WrappedClass(Param);
    }
}
 2
Author: Daniel Möller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-28 19:48:41

Czasami używam podejścia, które przypomina odpowiedzi za pomocą property injection, ale zachowuje czystość kodu. Zamiast bazowej klasy / interfejsu z zestawem właściwości, zawiera ona tylko (wirtualną) metodę Initialize (), która działa jako "konstruktor biednego człowieka". Następnie możesz pozwolić każdej klasie obsługiwać własną inicjalizację, tak jak zrobiłby to konstruktor, który również dodaje wygodny sposób obsługi łańcuchów dziedziczenia.

Jeśli często znajduję się w sytuacjach, w których chcę, aby każda klasa w łańcuchu inicjalizuje swoje unikalne właściwości, a następnie wywołuje metodę initialize () - rodzica, która z kolei inicjalizuje unikalne właściwości rodzica i tak dalej. Jest to szczególnie przydatne, gdy mają różne klasy, ale z podobną hierarchią, na przykład obiekty biznesowe, które są mapowane do / Z DTO: s.

Przykład, który używa Wspólnego Słownika do inicjalizacji:

void Main()
{
    var values = new Dictionary<string, int> { { "BaseValue", 1 }, { "DerivedValue", 2 } };

    Console.WriteLine(CreateObject<Base>(values).ToString());

    Console.WriteLine(CreateObject<Derived>(values).ToString());
}

public T CreateObject<T>(IDictionary<string, int> values)
    where T : Base, new()
{
    var obj = new T();
    obj.Initialize(values);
    return obj;
}

public class Base
{
    public int BaseValue { get; set; }

    public virtual void Initialize(IDictionary<string, int> values)
    {
        BaseValue = values["BaseValue"];
    }

    public override string ToString()
    {
        return "BaseValue = " + BaseValue;
    }
}

public class Derived : Base
{
    public int DerivedValue { get; set; }

    public override void Initialize(IDictionary<string, int> values)
    {
        base.Initialize(values);
        DerivedValue = values["DerivedValue"];
    }

    public override string ToString()
    {       
        return base.ToString() + ", DerivedValue = " + DerivedValue;
    }
}
 1
Author: Anders,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-30 13:53:07

To jest trochę brudne, a kiedy mówię trochę brudne, mogę mieć na myśli odrażające, ale przypuśćmy, że możesz podać swój parametryzowany Typ pustym konstruktorem, to:

public static T GetTInstance<T>() where T: new()
{
    var constructorTypeSignature = new Type[] {typeof (object)};
    var constructorParameters = new object[] {"Create a T"};
    return (T) new T().GetType().GetConstructor(constructorTypeSignature).Invoke(constructorParameters);
}

Pozwoli skutecznie skonstruować obiekt z parametryzowanego typu z argumentem. W tym przypadku zakładam, że konstruktor, którego chcę, ma pojedynczy argument typu object. Tworzymy dummy instancję T używając constraint permitted empty constructor, a następnie używamy reflection, aby uzyskać jedną z jej innych konstruktorów.

 0
Author: silasdavis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-12 11:38:39

Uważam, że musisz ograniczyć t za pomocą instrukcji where, aby zezwalać tylko na obiekty z nowym konstruktorem.

W tej chwili akceptuje wszystko, w tym obiekty bez niego.

 -4
Author: klkitchens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-08 15:08:05