Передача аргументов в C# generic new () шаблонного типа
Я пытаюсь создать новый объект типа T через его конструктор при добавлении в список.
Я получаю ошибку компиляции: сообщение об ошибке:
'T': не удается предоставить аргументы при создании экземпляра переменной
но мои классы имеют конструктор! Как я могу это сделать?
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.
}
...
}
13 ответов
чтобы создать экземпляр универсального типа в функции, вы должны ограничить его флагом "new".
public static string GetAllItems<T>(...) where T : new()
это будет работать только тогда, когда вы хотите вызвать конструктор, который не имеет параметров. Не в данном случае. Вместо этого вам нужно будет предоставить другой параметр, который позволяет создавать объект на основе параметров. Самый простой-это функция.
public static string GetAllItems<T>(..., Func<ListItem,T> del) {
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(del(listItem));
}
...
}
вы можете назвать это так
GetAllItems<Foo>(..., l => new Foo(l));
в .Net 3.5 и после того, как вы можете использовать класс активатора:
(T)Activator.CreateInstance(typeof(T), args)
поскольку никто не потрудился опубликовать ответ "отражение" (который я лично считаю лучшим ответом), вот:
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: этот ответ устарел из-за активатора .NET 3.5.CreateInstance, однако он по-прежнему полезен в старых версиях .NET.
объект инициализатор
Если ваш конструктор с параметром не делает ничего, кроме установки свойства, вы можете сделать это в C# 3, или лучше с помощью объект инициализатор вместо вызова конструктора (что невозможно, как уже было сказано):
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
}
...
}
используя это, вы всегда можете поместить любую логику конструктора в конструктор по умолчанию (пустой) , тоже.
активатор.CreateInstance ()
кроме того, вы можете позвонить активатор.CreateInstance () вот так:
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
}
...
}
обратите внимание, что активатор.Свойство может иметь несколько накладные расходы этого вы можете избежать, если скорость выполнения является главным приоритетом, а другой вариант доступен для вас.
это не будет работать в вашей ситуации. Вы можете указать только ограничение, которое имеет пустой конструктор:
public static string GetAllItems<T>(...) where T: new()
что вы можете сделать, это использовать инъекцию свойств, определив этот интерфейс:
public interface ITakesAListItem
{
ListItem Item { set; }
}
тогда вы можете изменить свой метод следующим образом:
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 });
}
...
}
другой альтернативой является Func
метод, описанный JaredPar.
очень старый вопрос, но новый ответ ;-)
версия ExpressionTree: (я думаю, что самые быстрые и чистые решения)
как Модель Welly Тамбунан сказал: "мы также можем использовать дерево выражений для создания объекта"
это создаст "конструктор" (функцию) для заданного типа/параметров. Он возвращает делегат и принимает типы параметров в виде массива объекты.
вот это:
// 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();
}
Пример 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;
}
}
использование:
// 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");
другой пример: передача типов в виде массива
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 слова
.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
.New TestExpressionConstructor.MainWindow+MyClass(
(System.Int32)$var1[0],
(System.String)$var1[1])
}
это эквивалентно коду, который сгенерировано:
public object myConstructor(object[] var1)
{
return new MyClass(
(System.Int32)var1[0],
(System.String)var1[1]);
}
недостаток
все параметры valuetypes упаковываются, когда они передаются как массив объектов.
простой тест производительности:
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();
результаты:
Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696
используя Expressions
is +/ -в 8 раз быстрее использования ConstructorInfo
и +/- в 20 раз быстрее использования Activator
вам нужно добавить where T: new (), чтобы компилятор знал, что T гарантированно предоставляет конструктор по умолчанию.
public static string GetAllItems<T>(...) where T: new()
Если вы просто хотите инициализировать член поле или свойство с параметром конструктора в C# >= 3 Вы можете сделать это очень просто:
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.
}
...
}
Я обнаружил, что получаю ошибку "не могу предоставить аргументы при создании экземпляра параметра типа T", поэтому мне нужно было сделать это:
var x = Activator.CreateInstance(typeof(T), args) as T;
если у вас есть доступ к классу, который вы будете использовать, вы можете использовать этот подход, который я использовал.
создайте интерфейс, который имеет альтернативного создателя:
public interface ICreatable1Param
{
void PopulateInstance(object Param);
}
сделайте свои классы с пустым создателем и реализуйте этот метод:
public class MyClass : ICreatable1Param
{
public MyClass() { //do something or nothing }
public void PopulateInstance (object Param)
{
//populate the class here
}
}
теперь используйте общие методы:
public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
//do stuff
T newT = new T();
T.PopulateInstance(Param);
}
если у вас нет доступа, оберните целевой класс:
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);
}
}
Я иногда использую подход, который напоминает ответы с помощью инъекции свойств, но сохраняет код чище. Вместо того, чтобы иметь базовый класс/интерфейс с набором свойств, он содержит только (виртуальный) Initialize()-метод, который действует как "конструктор бедняка". Затем вы можете позволить каждому классу обрабатывать собственную инициализацию так же, как конструктор, который также добавляет удобный способ обработки цепочек наследования.
Если часто оказываюсь в ситуациях, когда хочу каждый класс в цепочке инициализирует свои уникальные свойства, а затем вызывает метод Initialize () своего родителя, который, в свою очередь, инициализирует уникальные свойства родителя и т. д. Это особенно полезно при наличии разных классов, но с аналогичной иерархией, например бизнес-объектов, которые сопоставлены с / из DTO: s.
пример, который использует общий словарь для инициализации:
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;
}
}
это вроде как грязно, и когда я говорю "вроде грязно", я могу иметь в виду отвратительно, но предположим, что вы можете предоставить свой параметризованный тип пустым конструктором, тогда:
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);
}
эффективно позволит вам построить объект из параметризованного типа с аргументом. В этом случае я предполагаю, что конструктор, который я хочу, имеет один аргумент типа object
. Мы создаем фиктивный экземпляр T, используя ограничение, разрешенное пустым конструктором, а затем используем отражение, чтобы получить его из других конструкторов.
Я считаю, что вы должны ограничить T с помощью оператора where, чтобы разрешить только объекты с новым конструктором.
прямо сейчас он принимает все, включая объекты без него.