Как вы используете `switch` в C# для условного ветвления на основе только параметра типа?

Мой контекст заключается в том, что я создаю простой фабричный метод для создания экземпляров производных типов данного базового типа. Фабричный метод принимает только параметр типа, т.е. не имеет аргументов. Это, очевидно, возможно с конструкцией if-else if:

public Vehicle Create<T>()
{
    if (typeof(T) == typeof(Car))
        return new Car(); // just an example; could be more complex logic.
    else if (typeof(T) == typeof(Truck))
        return new Truck(); // just an example; could be more complex logic.
    else
        throw new ArgumentException(
            $"The type {typeof(T).Name} is not known by this method.");
}

К настоящему времени хорошо известно, как использовать сопоставление с образцом в C# (начиная с C# 7.0) для ветвления на основе типа переменной, но это не работает для параметра типа:

switch (T) { ... } // error: "'T' is a type, which is not valid in the given context"

or...

switch (typeof(T))
{
    case Car c: ... 
    // err: "An expression of type 'Type' cannot be handled by a pattern of type 'Car'"
}

Поэтому я хотел бы знать, можно ли использовать switch для достижения того же результата?


Исследование: я удивлен, что об этом раньше не спрашивали, но я не могу найти. Я нашел это сообщение, в котором есть название и несколько ответов, которые довольно близки, но это работа с (числовыми) типами значений и методами, которые имеют аргумент типа T -- параметр универсального типа. Точно так же этот пост также использует аргумент .


person rory.ap    schedule 09.04.2019    source источник
comment
Нет, боюсь, вы застряли с if/else. По крайней мере, помните, что JIT знает этот шаблон и может оптимизировать его.   -  person canton7    schedule 09.04.2019
comment
Ну, я ответил на свой вопрос, так что это возможно.   -  person rory.ap    schedule 09.04.2019
comment
Ваш ответ просто злоупотребляет переключателем для написания if/else - я не уверен, что вы действительно можете утверждать, что это жизнеспособная альтернатива if/else! Я мог бы написать if/else, используя фильтры исключений. Значит ли это, что я должен? Точно нет.   -  person canton7    schedule 09.04.2019
comment
включить тип с помощью отличный ответ   -  person qujck    schedule 09.04.2019
comment
Конечно, вы также можете создать Dictionary<Type, Func<Vehicle>> или что-то в этом роде.   -  person canton7    schedule 09.04.2019
comment
@ canton7 - это не будет почти такой же уровень эффективности - накладные расходы - как переключатель или else-if, так что это глупое предложение.   -  person rory.ap    schedule 09.04.2019
comment
Я не уверен, что понимаю ваше отношение. Вы спрашиваете о предложениях, затем называете их глупыми, но отстаиваете свое собственное предложение с некоторыми людьми, которые предпочли бы сделать это. Если вы собираетесь отвергать предложения всех остальных, но защищать свои до смерти, зачем вообще задавать вопрос?   -  person canton7    schedule 09.04.2019
comment
Кроме того, if/else — это O(n) (игнорируя любые JIT-оптимизации), а словарь — это O(1). Для большого количества элементов словарь, вероятно, выбьет штаны из if/else. Используйте RuntimeTypeHandle для бонусных очков скорости.   -  person canton7    schedule 09.04.2019
comment
Хорошо, я неправильно понял цель вашего комментария. Я думал, что это было сделано исключительно для того, чтобы указать, что вы считаете мой ответ абсурдным. Моя ошибка (честно).   -  person rory.ap    schedule 09.04.2019
comment
@qujck - вы неправильно поняли вопрос. Речь идет не о типе переменной, а о параметрах типа (дженериках).   -  person rory.ap    schedule 09.04.2019
comment
См. Есть ли лучшая альтернатива "включить тип"? для многих более абсурдных предложений, таких как where T : new() /* ... */ switch (new T()) { ... }.   -  person CodeCaster    schedule 09.04.2019
comment
См.: docs.microsoft.com/ en-us/dotnet/csharp/language-reference/   -  person Maximilian Burszley    schedule 09.04.2019
comment
@CodeCaster - как вопрос, который, по вашему мнению, является обманом, связан с параметрами универсального типа? Вы действительно думаете, что это точная копия того вопроса?   -  person rory.ap    schedule 09.04.2019


Ответы (2)


Я знаю, что ваш вопрос конкретно задавался об использовании оператора switch, но другой альтернативой было бы создание словаря заводов, связанных с типом.

Обратите внимание, что в этот момент вы выполняете операцию, аналогичную внедрению зависимостей. Вы запрашиваете Транспортное средство типа X и рискуете получить ошибку времени выполнения, если метод Create не имеет информации, необходимой для создания Транспортного средства типа X.

public class Car : Vehicle { }
public class Truck : Vehicle { }

public abstract class Vehicle
{
    private static readonly IReadOnlyDictionary<Type, Func<Vehicle>> vehicleFactories = new Dictionary<Type, Func<Vehicle>>
    {
        { typeof(Car), () => new Car() },
        { typeof(Truck), () => new Truck() }
    };

    public static Vehicle Create<T>() where T : Vehicle, new()
    {
        if (vehicleFactories.TryGetValue(typeof(T), out var factory))
        {
            return factory();
        }
        else
        {
            throw new ArgumentException(
                $"The type {typeof(T).Name} is not known by this method.");
        }
    }
}
person Grax32    schedule 11.04.2019

EDIT: Обратите внимание, что я не делаю никаких заявлений о том, хорошо это или плохо. Это полностью для того, чтобы показать, что это возможно сделать.

Этого можно добиться в C# 7.0 или более поздней версии, используя блок switch с сопоставлением с образцом и ключевое слово when:

public Vehicle Create<T>() where T : Vehicle
{
    switch (true)
    {
        case bool x when typeof(T) == typeof(Car): return new Car();
        case bool x when typeof(T) == typeof(Truck): return new Truck();
        default: throw new ArgumentException(
            $"The type {typeof(T).Name} is not known by this method.");
    }
}
person rory.ap    schedule 09.04.2019
comment
Это просто ужасно. Почему вы когда-нибудь захотите использовать это в своем коде? - person Patrick Hofman; 09.04.2019
comment
@PatrickHofman - это просто POC, и это полностью ваше мнение, если вы не можете подкрепить его чем-то фактическим. - person rory.ap; 09.04.2019
comment
В этом есть некая элегантность. Это тип вещей, которые растут на вас. - person rory.ap; 09.04.2019
comment
Извините, но, как написал canton7: ​​ваш ответ просто злоупотребляет переключателем, чтобы написать if/else. Вот именно. - person Patrick Hofman; 09.04.2019
comment
Но это возможно возможно. Этот вопрос не о том, хорошо это или плохо. Это полностью о том, возможно ли это. - person rory.ap; 09.04.2019
comment
Итак, на самом деле вопрос. Какие эзотерические способы существуют для написания if/else без использования явного if/else? Я могу придумать несколько ужасных ответов на этот вопрос... - person canton7; 09.04.2019
comment
Вы можете немного упростить свой вариант с помощью switch ((object)null) { case null when ...; case null when ...; } - person canton7; 09.04.2019
comment
@PatrickHofman - некоторые люди могут предпочесть использовать переключатель вместо if-else. И что? - person rory.ap; 09.04.2019
comment
@PatrickHofman Это вопросы и ответы одного и того же человека. Вы не можете определить, что они считают производством. - person Maximilian Burszley; 09.04.2019
comment
@ TheIncorrigible1 Нет. Я просто оцениваю полезность этого ответа. - person Patrick Hofman; 09.04.2019
comment
@PatrickHofman - ответ просто указывает на альтернативу if-else. Является ли это злоупотреблением С#, просто вопрос мнения. Если вы не можете указать на какое-то другое место, которое было упомянуто ранее, я думаю, что это может быть единственным местом и, следовательно, имеет некоторую степень полезности для кого-то, и это просто факт. - person rory.ap; 09.04.2019
comment
Проблема не в том, если/то против переключателя. Именно проверка типов во время выполнения приводит к непредсказуемым ошибкам во время выполнения. Если я пишу Create<Bicycle>(), и он компилируется, потому что Bicycle : Vehicle, но затем во время выполнения он говорит: "Ой, а не этот тип vehicle, это плохо. В большинстве (но не во всех) случаях, если мы выполняем такую ​​проверку типа во время выполнения, что-то пошло не так. - person Scott Hannen; 09.04.2019