2010-12-10 16 views
20

Estoy intentando escribir un método auxiliar que registre un mensaje y arroje una excepción de un tipo especificado con el mismo mensaje. Tengo el siguiente:Cómo crear una instancia de un argumento de tipo genérico utilizando un constructor parametrizado en C#

private void LogAndThrow<TException>(string message, params object[] args) where TException : Exception, new() 
{ 
    message = string.Format(message, args); 
    Logger.Error(message); 
    throw new TException(message); 
} 

Antes de añadir la restricción new() el compilador se quejó de que sin ella no puedo crear una instancia TException. Ahora el mensaje de error que recibo es "No puedo proporcionar argumentos al crear una instancia de un parámetro de tipo 'TException'". Intenté crear la instancia con el constructor sin parámetros y luego establecí la propiedad Message, pero es de solo lectura.

¿Es esto una limitación del idioma o hay una solución que no conozco? Tal vez podría usar la reflexión, pero eso es demasiado para una tarea tan simple. (Y bastante feo, pero eso es una cuestión de opinión personal)

+1

posible duplicado de [Crear instancia de tipo genérico ?] (http://stackoverflow.com/questions/731452/create-instance-of-generic-type) – nawfal

Respuesta

13

Puede usar Activator.CreateInstance() (que le permite pasar argumentos) para crear una instancia de TException. Entonces, podrías arrojar el TException creado.

Por ejemplo:

private void LogAndThrow<TException>(string message, params object[] args) where TException : Exception, new() 
{ 
    message = string.Format(message, args); 
    Logger.Error(message); 

    TException exception = (TException)Activator.CreateInstance(typeof(TException), message); 
    throw exception; 
} 
+0

Marqué esto como aceptado porque la solución de JaredPar puede ser más rápida pero esta envuelve el hacker dentro del método y es más elegante desde el punto de vista de la persona que llama. – neo2862

4

Esto es una limitación de la restricción genérico new. Solo se puede usar para crear objetos a través del constructor sin parámetros.

Una forma de evitar esto es proporcionar un método de fábrica lambda que tome los parámetros apropiados. En el sitio de la llamada puede diferir al constructor de la clase

private void LogAndThrow<TException>(
    Func<string,TException> func, 
    string message, 
    params object[] args) where TException : Exception {  

    message = string.Format(message, args);  
    Logger.Error(message); 
    throw func(message); 
} 

LogAndThrow(msg => new InvalidOperationException(msg), "my message"); 
8

Sí, eso es una limitación; no hay construcciones de lenguaje para eso.

Mi recomendación en este caso sería crear un delegado mecanografiado al constructor por tipo; caché que delega (por lo general en un campo estático de un tipo genérico, para mayor comodidad) y reutilizarlo. Puedo proporcionar un ejemplo más adelante, pero no puedo hacerlo desde el iPod;)

I cree He asignado algún código para esto en la biblioteca MiscUtil de Jon Skeet; para que puedas mirar allí también


Como solicitados (comentarios), aquí es una forma de hacer esto - en este caso utilizando la API Expression. Tenga en cuenta, en particular, el uso de las clases genéricas anidados que aseguran que hacemos la reflexión/compilación como máximo una vez por tipo de combinación:

using System; 
using System.Linq.Expressions; 

class Program { 
    static void Main() { 
     var ctor = TypeFactory.GetCtor<int, string, DemoType>(); 

     var obj = ctor(123, "abc"); 
     Console.WriteLine(obj.I); 
     Console.WriteLine(obj.S); 
    } 
} 

class DemoType { 
    public int I { get; private set; } 
    public string S { get; private set; } 
    public DemoType(int i, string s) { 
     I = i; S = s; 
    } 
} 

static class TypeFactory { 
    public static Func<T> GetCtor<T>() { return Cache<T>.func; } 
    public static Func<TArg1, T> GetCtor<TArg1, T>() { return Cache<T, TArg1>.func; } 
    public static Func<TArg1, TArg2, T> GetCtor<TArg1, TArg2, T>() { return Cache<T, TArg1, TArg2>.func; } 
    private static Delegate CreateConstructor(Type type, params Type[] args) { 
     if(type == null) throw new ArgumentNullException("type"); 
     if(args == null) args = Type.EmptyTypes; 
     ParameterExpression[] @params = Array.ConvertAll(args, Expression.Parameter); 
     return Expression.Lambda(Expression.New(type.GetConstructor(args), @params), @params).Compile(); 

    } 
    private static class Cache<T> { 
     public static readonly Func<T> func = (Func<T>)TypeFactory.CreateConstructor(typeof(T)); 
    } 
    private static class Cache<T, TArg1> { 
     public static readonly Func<TArg1, T> func = (Func<TArg1, T>)TypeFactory.CreateConstructor(typeof(T), typeof(TArg1)); 
    } 
    private static class Cache<T, TArg1, TArg2> { 
     public static readonly Func<TArg1, TArg2, T> func = (Func<TArg1, TArg2, T>)TypeFactory.CreateConstructor(typeof(T), typeof(TArg1), typeof(TArg2)); 
    } 
} 
+0

¿Puede decirme cómo lo haría? Suena muy interesante. – neo2862

+0

@ neo2862 probablemente a través de Expression API: estaré en una PC en aproximadamente 2 horas; se publicará entonces –

+0

@ neo2862 agregando ahora ... –

1

probar esto

private void ThrowAndLog<TException>(string message, params object[] args) 
    where TException : Exception, new() 
{ 
    message = string.Format(message, args); 
    Logger.Error(message); 
    throw (TException)typeof(TException).GetConstructor(
     new[] { typeof(string) }).Invoke(new object[] { message }); 
} 
+0

Genérico es para acelerar el tiempo de ejecución, sin crear el tiempo de ejecución. –

Cuestiones relacionadas