Подтвердить что ты не робот

Как создать экземпляр аргумента общего типа с использованием параметризованного конструктора в С#

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

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, но он доступен только для чтения.

Это ограничение языка или есть решение, о котором я не знаю? Может быть, я мог бы использовать рефлексию, но это переполнение для такой простой задачи. (И довольно уродливый, но это вопрос личного мнения.)

4b9b3361

Ответ 1

Вы можете использовать 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;
}

Ответ 2

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

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

Я полагаю, что я сделал некоторый код для этого в библиотеке Jon Skeet MiscUtil; так что вы тоже можете посмотреть там.


В соответствии с запросом (комментариями), это способ сделать это - в этом случае с использованием API Expression. Обратите внимание, в частности, на использование вложенных родовых классов, которые гарантируют, что мы будем делать отражение/компиляцию не более одного раза за комбинацию типов:

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

Ответ 3

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

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

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

Ответ 4

попробуйте это

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