Переключение на основе общего типа аргумента
В C# 7.1 ниже действительный код:
object o = new object();
switch (o)
{
case CustomerRequestBase c:
//do something
break;
}
Однако я хочу использовать оператор переключения шаблонов в следующем сценарии:
public T Process<T>(object message, IMessageFormatter messageFormatter)
where T : class, IStandardMessageModel, new()
{
switch (T)
{
case CustomerRequestBase c:
//do something
break;
}
}
Среда IDE выдает мне ошибку: "T - это тип, который недопустим в данном контексте". Есть ли элегантный способ включить тип универсального параметра? Я понял, что в моем первом примере вы включаете объект, а во втором я бы хотел включить тип T. Каков наилучший подход для этого?
5 ответов
Ниже приведены два разных класса, называемые Foo и Bar. Вы можете использовать один экземпляр любого из этих классов в качестве параметра для функции с именем Process. В конце концов, вы можете выполнить сопоставление с образцом, как показано в примере функции. Для примера использования есть функция Test.
public class Foo
{
public string FooMsg { get; set; }
}
public class Bar
{
public string BarMsg { get; set; }
}
public class Example
{
public T Process<T>(T procClass) where T : class
{
switch (typeof(T))
{
case
var cls when cls == typeof(Foo):
{
var temp = (Foo)((object)procClass);
temp.FooMsg = "This is a Foo!";
break;
}
case
var cls when cls == typeof(Bar):
{
var temp = (Bar)((object)procClass);
temp.BarMsg = "This is a Bar!";
break;
}
}
return
procClass;
}
public void Test(string message)
{
Process(new Foo() { FooMsg = message });
Process(new Bar() { BarMsg = message });
}
}
Я согласен с тем, что бывают ситуации, когда этот подход работает быстрее и не так уродлив, а также согласен с тем, что в любом случае следует найти лучшее решение, но иногда компромисс не окупается ... так что вот решение (C# 9.0)
return typeof(T) switch
{
Type t when t == typeof(CustomerRequestBase) => /*do something*/ ,
_ => throw new Exception("Nothing to do")
};
Если мы проигнорируем обсуждение codemell в соответствии с комментариями, легко читаемая реализация (хак) может выглядеть так:
public T Process<T>(string number)
{
switch (typeof(T).FullName)
{
case "System.Int32":
return (dynamic) int.Parse(number);
case "System.Double":
return (dynamic) double.Parse(number);
default:
throw new ArgumentException($"{typeof(T).FullName} is not supported");
}
}
Даже если вы # со своими общими ограничениями, это, вероятно, вызовет проблемы, если только вы не являетесь единственным программистом ;)
Я использовал typeof(T), typeof(T) t if t == typeof(X), typeof(T).Name и typeof(T).FullName варианты (предлагается в других ответах), но я никогда был счастлив с любым из них. Они либо слишком сложны, либо слишком медленны.
typeof(T) When t == typeof(X), вероятно, является лучшим, однако его производительность сомнительна, поскольку компилятор, похоже, рассматривает предложения When как серию операторов if... else if.... Запустите отладчик и проследите его, чтобы понять, что я имею в виду. Варианты строк имеют схожие проблемы: получение имен типов и сравнение строк кажутся ненужными накладными расходами.
Что нам действительно нужно, так это решение, которое использует собственное поведение поиска хеша переключателя для оптимальной производительности.
Итак, вот еще одно решение, которое уменьшает сложность:
- Создайте фиктивную переменную T
- Присвойте новое значение
- Используйте фиктивную переменную типа T в операторе переключения.
Результат:
public T Process<T>(object message, IMessageFormatter messageFormatter)
where T : class, IStandardMessageModel, new()
{
T dummy = Activator.CreateInstance(typeof(T));
switch (dummy)
{
case CustomerRequestBase _:
//do something
break;
}
}
T требует конструктор, который в данном случае идеален, поскольку метод квалифицируется с помощью где T: class, new() — класс и имеющий конструктор по умолчанию. Таким образом, мы можем создать экземпляр фиктивной переменной и использовать эту фиктивную переменную для выполнения стандартной функциональности типа переменной-переключателя.
Предупреждение: T dummy = default не будет работать, поскольку значение по умолчанию обычно равно Null, и мы не можем использовать Null в операторе переключения. Вот почему был использован Activator.CreateInstance().
Отбросить (_) в операторе case используется для предотвращения использования фиктивной переменной. Предположительно, оператор случая будет иметь другую логику для обработки «сообщения».
Я собираюсь в предисловии сказать, что в целом я согласен со всеми комментаторами, которые говорят, что переключение на общий T, вероятно, не очень хорошая идея. В этом случае я бы посоветовал ему придерживаться идентификации объекта, его приведения и передачи соответствующему обработчику.
Тем не менее, я писал собственный двоичный сериализатор, который должен быть достаточно эффективным, и я обнаружил один случай, когда я чувствую, что тип переключения (или оператора if), о котором он просит, оправдан, так что вот как я справился с этим.
public T ProcessAs<T>(parameters)
{
if (typeof(T) == typeof(your_type_here)
{
your_type_here tmp = process(parameters);
return Unsafe.As<your_type_here, T>(ref tmp);
}
else if (typeof(T) == typeof(your_other_type))
{
your_other_type tmp = otherProcess(parameters);
return Unsafe.As<your_other_type, T>(ref tmp);
}
else
{
throw new ArgumentException(appropriate_msg);
}
}
Обратите внимание, что