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

21

我正在尝试编写一个助手方法,它会记录一条消息并抛出指定类型的异常,该异常具有相同的消息。我有以下代码:

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属性,但它是只读的。
这是语言的限制还是我不知道的解决方法?也许我可以使用反射,但这对于如此简单的任务来说过于复杂。(而且相当丑陋,但这是个人意见问题。)

1
可能是创建泛型类型的实例?的重复问题。 - nawfal
4个回答

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

我将此标记为已接受,因为JaredPar的解决方案可能更快,但这个解决方案将hackery封装在方法内部,从调用者的角度来看更加优雅。 - neo2862

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

你能告诉我你会如何做吗?听起来很有趣。 - neo2862
@neo2862 可能通过 Expression API 进行 - 我大约两个小时后会在电脑旁,届时会发布。 - Marc Gravell
这太棒了。特别是您如何使用构建的嵌套类型的静态字段进行缓存。如果可以的话,我会给它点赞10次。 - neo2862
@neo2862 - 这也提供了可靠的线程安全性,这很好;p - Marc Gravell
这与只需执行 System.Activator.CreateInstance(typeof(T), param1, param2, param3) 的用例相比如何?缓存此查找到的确切构造函数是否有好处? - ErikE

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

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

泛型是为了加速运行时而不是进行运行时创建。 - Saeed Amiri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接