Как можно уйти от монструозных switch case?

В общем, есть метод:

public BaseType CreateType(SomeEnum enum,SomeParam param,SomeParam2 param2)
{
  switch (enum)
            {
                case SomeEnum.enum1:
                case SomeEnum.enum2:
                case SomeEnum.enum3:
                    return new SomeType1(param.Param1,param.Param2)
                case SomeEnum.enum4:
                    var val=param.Param1+param.Param2;
                    return new SomeType2(val,param2.Param1)
                 ...

(Немного обфусцировал его, но суть должна быть понятна)

Проблема в большом switch и добавляя новые значения в перечисления он становится все больше и больше...

Как от этого можно элегантно уйти? Видел в интернете решения через словарь <enum,action>, но кардинально ничего не поменяется.


Ответы (2 шт):

Автор решения: Pavel Mayorov

Для начала надо что-то сделать с параметрами - у вас их слишком много, такое количество параметров по цепочке передавать неудобно:

class CreateTypeContext
{
    public SomeParam Param { get; set; }
    public SomeParam2 Param2 { get; set; }

    // также этот класс можно сделать иммутабельным или вообще record type из C# 9.0 - к обсуждаемому вопросу это отношения не имеет
}

Дальше в простейшем случае этот контекст можно передавать прямо в конструкторы конкретных типов. Ну или же можно сделать по отдельному методу на конкретный тип:

BaseType CreateType1 (CreateTypeContext context)
{
     var val = context.Param1.Foo + context.Param1.Bar;
     return new SomeType2(val, context.Param2.Baz);
}

Дальше можно сделать пользовательской атрибут и навесить его на метод - это позволит уйти от конструкции switch (если вы передаёте контекст в конструктор - атрибут лучше навешивать на класс):

[AttributeUsage(AttributeTargets.Method)]
sealed class SomeEnumAttribute : Attribute
{
    public IReadOnlyCollection<SomeEnum> Kinds { get; }

    public SomeEnumAttribute(params SomeEnum[] kinds)
    {
        Kinds = kinds;
    }
}

// …

[SomeEnum(SomeEnum.enum4)]
BaseType CreateType1 (CreateTypeContext context) { … }

Теперь осталось только получить список всех методов через рефлексию, найти среди них метод с нужным атрибутом и вызвать его. Только если вы так будете делать многократно - лучше соберите заранее словарь делегатов:

class BaseTypeFactory
{
    private readonly Dictionary<SomeEnum, Func<CreateTypeContext, BaseType>> factories;

    public BaseTypeFactory()
    {
        factories = (
            from method in GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
            let attr = method.GetCustomAttribute<SomeEnumAttribute>()
            where attr != null
            let fn = (Func<CreateTypeContext, BaseType>)Delegate.CreateDelegate(typeof(Func<CreateTypeContext, BaseType>)), this, method)
            from kind in attr.Kinds
            select (kind, fn)
       ).ToDictionary(x => x.kind, x => x.fn);
    }

    public BaseType CreateType(SomeEnum kind, SomeParam1 param1, SomeParam2 param2)
    {
         var context = new CreateTypeContext
         {
             Param1 = param1,
             Param2 = param2,
         };

         if (factories.TryGetValue(kind, out var factory))
             return factory(context);

         // …
    }

    // методы создания
}
→ Ссылка
Автор решения: NaClnik

Не уверен, что это будет подходить конкретно для Вашего случая, но есть вариант использовать паттерн Strategy.

using System;
                    
public class Program
{
    public static void Main()
    {
        TypeCreator typeCreator = new TypeCreator();
        
        typeCreator.SetStrategy(new Strategy1());
        
        var a = typeCreator.CreateType(new int[] {1});
        Console.WriteLine(a);
        
        typeCreator.SetStrategy(new Strategy2());
        
        var b = typeCreator.CreateType(new int[] {1, 2});
        Console.WriteLine(b);
    }
    
    abstract class BaseType{}
        
    class SomeType1 : BaseType{
        private int _a;
        
        public SomeType1(int a){
            this._a = a;
        }
        
        public override string ToString() { return _a.ToString();}
    }
    class SomeType2 : BaseType{
        private int _a;
        private int _b;
        
        public SomeType2(int a, int b){
            this._a = a;
            this._b = b;
        }
        
        public override string ToString() { return _a.ToString() + " " + _b.ToString();}
    }

    
    class TypeCreator {
        private IStrategy _strategy;
        
        public void SetStrategy(IStrategy strategy){
            this._strategy = strategy;
        }
        
        public BaseType CreateType(int[] parameters) {
            return _strategy.Execute(parameters);
        }
    }
    
    interface IStrategy {
        BaseType Execute(int[] parameters);
    }
    
    class Strategy1 : IStrategy{
        public BaseType Execute(int[] parameters){
            return new SomeType1(parameters[0]);
        }
    }
    
    class Strategy2 : IStrategy{
        public BaseType Execute(int[] parameters){
            return new SomeType2(parameters[0], parameters[1]);
        }
    }
}
→ Ссылка