Как создать экземпляр аргумента универсального типа, используя параметризованный конструктор в C # - PullRequest
21 голосов
/ 10 декабря 2010

Я пытаюсь написать вспомогательный метод, который бы регистрировал сообщение и выдавал исключение указанного типа с тем же сообщением.У меня есть следующее:

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 ]

14 голосов
/ 10 декабря 2010

Вы можете использовать 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;
}
8 голосов
/ 10 декабря 2010

Да, это ограничение;для этого не существует языковой конструкции.

В этом случае я бы рекомендовал создать типизированный делегат для конструктора для каждого типа;кэшируйте этот делегат (обычно для удобства в статическом поле универсального типа) и используйте его повторно.Я могу привести пример позже - но я не могу сделать это с iPod;)

Я , верю Я добавил этот код в библиотеку Джона Скита 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));
    }
}
4 голосов
/ 10 декабря 2010

Это ограничение общего ограничения new. Он может использоваться только для создания объектов через конструктор без параметров.

Одним из способов решения этой проблемы является предоставление лямбда-фабричного метода, который принимает соответствующие параметры. На сайте вызова его можно отложить до конструктора класса

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 голос
/ 10 декабря 2010

попробуйте

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 });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...