如何在C#中使用参数化构造函数创建泛型类型参数的实例

时间:2010-12-10 16:38:11

标签: c# generics exception

我正在尝试编写一个帮助器方法,该方法将记录消息并使用相同的消息抛出指定类型的异常。我有以下内容:

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

在添加new()约束之前,编译器抱怨说没有它我就无法实例化TException。现在我收到的错误消息是“在创建类型参数'TException'的实例时无法提供参数”。我尝试使用无参数构造函数创建实例,然后设置Message属性,但它是只读的。

这是语言的限制还是有一个我不知道的解决方案?也许我可以使用反射,但这对于这么简单的任务来说太过分了。 (而且非常难看,但这是个人观点的问题。)

4 个答案:

答案 0 :(得分:14)

您可以使用Activator.CreateInstance()(允许您传入参数)来创建TException的实例。然后,您可以抛出创建的TException

例如:

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

答案 1 :(得分:8)

是的,这是一个限制;没有语言结构。

我在这种情况下的建议是为每个类型的构造函数创建一个类型委托;缓存该委托(为方便起见,通常在泛型类型的静态字段中)并重新使用它。我可以稍后提供一个例子 - 但我不能从iPod做到这一点;)

相信我为Jon Skeet的MiscUtil库提交了一些代码;所以你也可以在那里看看。


根据要求(评论),这是一种方法 - 在这种情况下使用Expression API。请特别注意嵌套泛型类的使用,这些类确保每个类型组合最多进行一次反射/编译:

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

答案 2 :(得分:4)

这是通用约束new的限制。它只能用于通过无参数构造函数创建对象。

解决此问题的一种方法是提供一个lambda工厂方法,该方法采用适当的参数。在调用站点,它可以遵循类构造函数

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

答案 3 :(得分:1)

试试这个

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