Как создать экземпляр аргумента общего типа с использованием параметризованного конструктора в С#
Я пытаюсь написать вспомогательный метод, который регистрирует сообщение и выдает исключение указанного типа с тем же сообщением. У меня есть следующее:
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
Вы можете использовать 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 });
}