programacion - Pasar argumentos a C#new genérico() de tipo de plantilla
parametros programacion c# (13)
A veces utilizo un enfoque que se asemeja a las respuestas que utilizan la inyección de propiedades, pero mantiene el código más limpio. En lugar de tener una clase / interfaz base con un conjunto de propiedades, solo contiene un método Initialize (virtual) (virtual) que actúa como un "constructor de hombres pobres". Luego, puede dejar que cada clase maneje su propia inicialización como lo haría un constructor, lo que también agrega una forma conveniente de manejar las cadenas de herencia.
Si a menudo me encuentro en situaciones en las que quiero que cada clase en la cadena inicialice sus propiedades únicas, y luego llame a su método principal, Initialize (), que a su vez inicializa las propiedades únicas de los padres, etc. Esto es especialmente útil cuando se tienen diferentes clases, pero con una jerarquía similar, por ejemplo, los objetos comerciales que se asignan a / desde DTO: s.
Ejemplo que usa un diccionario común para la inicialización:
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;
}
}
Estoy intentando crear un nuevo objeto de tipo T a través de su constructor al agregar a la lista.
Recibo un error de compilación: el mensaje de error es:
''T'': no puede proporcionar argumentos al crear una instancia de una variable
¡Pero mis clases tienen un argumento de constructor! ¿Cómo puedo hacer que esto funcione?
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.
}
...
}
Creo que tienes que restringir T con una instrucción where para permitir solo objetos con un nuevo constructor.
Justo ahora acepta cualquier cosa incluyendo objetos sin él.
Debe agregar el lugar donde T: new () para que el compilador sepa que T tiene garantizado un constructor predeterminado.
public static string GetAllItems<T>(...) where T: new()
Descubrí que estaba recibiendo un error "no puedo proporcionar argumentos al crear una instancia del tipo de parámetro T", así que tuve que hacer esto:
var x = Activator.CreateInstance(typeof(T), args) as T;
Esto es una especie de mala suerte, y cuando digo una especie de mala suerte me refiero a rebelarme, pero suponiendo que pueda proporcionar su tipo parametrizado con un constructor vacío, entonces:
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);
}
Le permitirá efectivamente construir un objeto a partir de un tipo parametrizado con un argumento. En este caso, asumo que el constructor que quiero tiene un solo argumento de tipo object
. Creamos una instancia ficticia de T utilizando el constructor vacío permitido de restricción y luego usamos la reflexión para obtener uno de sus otros constructores.
Esto no funcionará en su situación. Solo puede especificar la restricción de que tiene un constructor vacío:
public static string GetAllItems<T>(...) where T: new()
Lo que podría hacer es usar la inyección de propiedades definiendo esta interfaz:
public interface ITakesAListItem
{
ListItem Item { set; }
}
Entonces podrías alterar tu método para ser este:
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 });
}
...
}
La otra alternativa es el método Func
descrito por JaredPar.
Muy antigua pregunta, pero nueva respuesta ;-)
La versión de ExpressionTree : (Creo que la solución más rápida y limpia)
Como dijo Welly Tambunan , "también podríamos usar el árbol de expresiones para construir el objeto"
Esto generará un ''constructor'' (función) para el tipo / parámetros dados. Devuelve un delegado y acepta los tipos de parámetros como una matriz de objetos.
Aquí está:
// 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();
}
Ejemplo 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;
}
}
Uso:
// 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");
Otro ejemplo: pasar los tipos como una matriz
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 de expresión
.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
.New TestExpressionConstructor.MainWindow+MyClass(
(System.Int32)$var1[0],
(System.String)$var1[1])
}
Esto es equivalente al código que se genera:
public object myConstructor(object[] var1)
{
return new MyClass(
(System.Int32)var1[0],
(System.String)var1[1]);
}
Pequeño inconveniente
Todos los parámetros valuetypes están encuadrados cuando se pasan como una matriz de objetos.
Prueba de rendimiento simple:
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();
Resultados:
Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696
Usar Expressions
es +/- 8 veces más rápido que Invocar a ConstructorInfo
y +/- 20 veces más rápido que usar el Activator
Para crear una instancia de un tipo genérico en una función, debe restringirla con el indicador "nuevo".
public static string GetAllItems<T>(...) where T : new()
Sin embargo, eso solo funcionará cuando desee llamar al constructor que no tiene parámetros. No es el caso aquí. En su lugar, tendrá que proporcionar otro parámetro que permita la creación de objetos basados en parámetros. Lo más fácil es una función.
public static string GetAllItems<T>(..., Func<ListItem,T> del) {
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(del(listItem));
}
...
}
Entonces puedes llamarlo así
GetAllItems<Foo>(..., l => new Foo(l));
Si simplemente desea inicializar un campo o propiedad miembro con el parámetro constructor, en C #> = 3 puede hacerlo más fácilmente:
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.
}
...
}
Esto es lo mismo que dijo Garry Shutler, pero me gustaría poner una nota adicional.
Por supuesto, puede usar un truco de propiedad para hacer más cosas que simplemente establecer un valor de campo. Una propiedad "set ()" puede desencadenar cualquier procesamiento necesario para configurar los campos relacionados y cualquier otra necesidad para el objeto en sí, incluyendo una verificación para ver si se debe realizar una inicialización completa antes de que se use el objeto, simulando una construcción completa ( sí, es una solución fea, pero supera la nueva limitación de M $ ().
No puedo estar seguro de que sea un hoyo planificado o un efecto secundario accidental, pero funciona.
Es muy divertido cómo M $ people agrega nuevas funciones al lenguaje y parece que no realiza un análisis completo de los efectos secundarios. Todo lo genérico es una buena evidencia de esto ...
Si tiene acceso a la clase que va a usar, puede usar este enfoque que usé.
Crea una interfaz que tenga un creador alternativo:
public interface ICreatable1Param
{
void PopulateInstance(object Param);
}
Realiza tus clases con un creador vacío e implementa este método:
public class MyClass : ICreatable1Param
{
public MyClass() { //do something or nothing }
public void PopulateInstance (object Param)
{
//populate the class here
}
}
Ahora usa tus métodos genéricos:
public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
//do stuff
T newT = new T();
T.PopulateInstance(Param);
}
Si no tiene acceso, envuelva la clase de destino:
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);
}
}
Ya que nadie se molestó en publicar la respuesta ''Reflexión'' (que personalmente creo que es la mejor respuesta), aquí va:
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);
}
...
}
Edición: esta respuesta está en desuso debido a Activator.CreateInstance de .NET 3.5, sin embargo, sigue siendo útil en versiones anteriores de .NET.
en .Net 3.5 y después de usar la clase de activador:
(T)Activator.CreateInstance(typeof(T), args)
Inicializador de objetos
Si su constructor con el parámetro no hace nada más que establecer una propiedad, puede hacerlo en C # 3 o mejor usando un inicializador de objetos en lugar de llamar a un constructor (lo cual es imposible, como se ha mencionado):
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
}
...
}
Usando esto, siempre puede poner cualquier lógica de constructor en el constructor predeterminado (vacío), también.
Activator.CreateInstance ()
Alternativamente, puedes llamar a Activator.CreateInstance() así:
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
}
...
}
Tenga en cuenta que Activator.CreateInstance puede tener una sobrecarga de rendimiento que puede evitar si la velocidad de ejecución es una prioridad máxima y otra opción es mantenible para usted.