2010-12-10 5 views
20

J'essaye d'écrire une méthode d'aide qui enregistrerait un message et lancerait une exception d'un type spécifié avec le même message. Je donne les résultats suivants:Comment créer une instance d'un argument de type générique à l'aide d'un constructeur paramétré 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); 
} 

Avant d'ajouter la contrainte new() le compilateur se plaint que sans elle, je ne peux pas instancier TException. Maintenant, le message d'erreur que je reçois est "Impossible de fournir des arguments lors de la création d'une instance d'un paramètre de type 'TException'". J'ai essayé de créer l'instance avec le constructeur sans paramètre, puis j'ai défini la propriété Message mais elle est en lecture seule.

Est-ce une limitation du langage ou existe-t-il une solution que je ne connais pas? Peut-être que je pourrais utiliser la réflexion, mais c'est trop pour une tâche aussi simple. (Et assez moche, mais c'est une question d'opinion personnelle.)

+1

double possible de [Créer une instance de type générique ?] (http://stackoverflow.com/questions/731452/create-instance-of-generic-type) – nawfal

Répondre

13

Vous pouvez utiliser Activator.CreateInstance() (qui vous permet de passer des arguments) pour créer une instance de TException. Ensuite, vous pouvez lancer le TException créé.

Par exemple:

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

J'ai marqué ceci comme accepté parce que la solution de JaredPar peut être plus rapide mais celle-ci enveloppe le hackery à l'intérieur de la méthode et est plus élégante du point de vue de l'appelant. – neo2862

4

C'est une limitation de la contrainte générique new. Il ne peut être utilisé que pour créer des objets via le constructeur sans paramètre. Une façon de contourner ce problème est de fournir une méthode d'usine lambda qui prend les paramètres appropriés. Au niveau du site d'appel, il peut s'en remettre au constructeur de classe

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

Oui, c'est une limitation; il n'y a pas de construction de langage pour cela.

Ma recommandation dans ce cas serait de créer un dactylographié délégué au constructeur par type; mettre en cache ce délégué (généralement dans un champ statique d'un type générique, par commodité) et le réutiliser. Je peux fournir un exemple plus tard - mais je ne peux pas le faire à partir de l'iPod;)

I crois J'ai commis du code pour cela dans la bibliothèque MiscUtil de Jon Skeet; donc vous pouvez regarder là aussi.


Comme l'a demandé (commentaires), voici une façon de le faire - dans ce cas, en utilisant l'API Expression. Notez en particulier l'utilisation des classes génériques imbriquées qui garantissent que nous faisons la réflexion/compilation au plus une fois par type combinaison:

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

Pouvez-vous me dire comment vous le feriez? Cela semble très intéressant. – neo2862

+0

@ neo2862 probablement via l'API Expression - je serai à un PC dans environ 2 heures; affichera alors –

+0

@ neo2862 en ajoutant maintenant ... –

1

essayer cette

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

Générique est pour accélérer le temps d'exécution, ne pas créer d'exécution. –

Questions connexes