开发者

How to create an instance of a generic type argument using a parameterized constructor in C#

I'm trying to write a helper method that would log a message and throw an exception of a specified type with the same message. I have the following:

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);
}

Before adding the new() constraint the compiler complained that without it I can't instantiate TException. Now the error message I get is "Cannot provide arguments when creating an instance of a type parameter 'TException'". I tried creating the instance with the parameterless constructo开发者_如何学Cr and then set the Message property but it's read-only.

Is this a limitation of the language or is there a solution I don't know about? Maybe I could use reflection but that's overkill for such a simple task. (And pretty ugly, but that's a matter of personal opinion.)


You can use Activator.CreateInstance() (which allows you to pass in arguments) to create an instance of TException. Then, you could throw the created TException.

For example:

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;
}


Yes, that is a limitation; there is no language construct for that.

My recommendation in this case would be to create a typed delegate to the constructor per-type; cache that delegate (usually in a static field of a generic type, for convenience) and re-use it. I can provide an example later - but I can't do it from iPod ;)

I believe I committed some code for this into Jon Skeet's MiscUtil library; so you could look there too.


As requested (comments), here is a way of doing this - in this case using the Expression API. Note in particular the use of the nested generic classes that ensure we do the reflection / compilation at most once per type-combination:

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));
    }
}


This is a limitation of the generic constraint new. It can only be used to create objects through the parameterless constructor.

One way to work around this is to provide a lambda factory method which takes the appropriate parameters. At the call site it can defer to the class constructor

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");


try this

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜