简体   繁体   English

C#替换了巨大的if-else语句

[英]C# replacing huge if-else statement

let's say i have Item object witch mostly holds enum properties like this 假设我有Item对象,它主要拥有这样的枚举属性

public enum Shape
{
    square = 1,
    trangle = 2,
    any = 3
}

public enum Color
{
    blue = 1,
    red = 2,
    yellow = 3,
    green = 4
}


public enum Material
{
    glass = 1,
    wood = 2,
    metal = 3
}


public class Item
{
    public Shape ItemShape { get; set; }
    public Color ItemColor { get; set; }
    public Material ItemMaterial { get; set; }
}

What am trying to do is depends of combination of whole three properties i need to do some action later; 我想要做的是取决于整个三个属性的组合,我需要稍后做一些动作;

I was thinking to use if-else combination like: 我正在考虑使用if-else组合,如:

if(item.ItemShape == Shape.square && item.ItemColor == Color.blue && item.ItemMaterial == Material.glass)
        {
            //call Action1
        }
        else if(item.ItemShape == Shape.square && item.ItemColor == Color.blue && item.ItemMaterial == Material.wood)
        {
            // action2
        }
        ......

Problem is that i have around 16 of combinations, so it will be huge if-else method to resolve what method should i call later. 问题是我有大约16个组合,所以它将是巨大的if-else方法来解决我应该稍后调用的方法。

在此输入图像描述

Maybe is there any other way to replace if-else statements more readable code, design patter or something more efficient? 也许是否有任何其他方法来替换if-else语句更可读的代码,设计模式或更高效的东西?

I was thinking to combine whole possible states as flag enum values, but not sure if i can create enum value from object property later. 我想将整个可能的状态组合为标记枚举值,但不确定我是否可以稍后从对象属性创建枚举值。

It sounds like you want some rule set to check an item against. 听起来你想要一些规则集来检查项目。 I think the simplest form to make this more readable, is to pass the item, the properties of the rule, and the action to a separate method: 我认为使这个更具可读性的最简单的形式是将项目,规则的属性和操作传递给单独的方法:

    public bool RunActionIf(Item item, Shape shape, Color color, Material material, Action action)
    {
        if(item.ItemShape == shape && item.ItemColor == color && item.ItemMaterial == material)
        {
            action();
            return true;
        }

        return false;
    }

    public void RunAction(Item item)
    {
        var result =
            RunActionIf(item, Shape.square, Color.blue, Material.glass, Action1) ||
            RunActionIf(item, Shape.square, Color.blue, Material.wood, Action2) ||
            /* Implement your whole table like this */;

        if(!result)
        {
            throw new ArgumentException("No matching rule found", nameof(item));
        }
    }

The main advantage of that method is that it's shorter and with less 'overhead' in the declaration. 该方法的主要优点是它更短,并且声明中的“开销”更少。 You can easily see: shape X + color Y + material Z= that action. 你可以很容易地看到:形状X +颜色Y +材料Z =那个动作。

Another advantage is that it's easier to implement certain exceptions, for instance by allowing one of the rule parameters to be null to indicate any color, or to use Color.any to support that, although I think it's confusing to have any in the enum with other colors... Anyway, I digress. 另一个优点是实现某些异常更容易,例如允许其中一个规则参数为null来指示任何颜色,或者使用Color.any来支持它,尽管我认为在枚举中有any异常是令人困惑的。其他颜色......无论如何,我离题了。 The point is, if you want to implement that, you have to do that only in one place without having to copy it 16 times. 关键是,如果你想实现它,你必须只在一个地方做到这一点,而不必复制16次。

You could abstract this away a bit further by making such a rule a separate object, which you can put in a list or dictionary, but for a definition like this, it doesn't make it that much more readable, although it does add some benefits of testibility, as well as the possibility to add different kinds of rules, without messing up your clean list again. 你可以抽象送人有点进一步作出这样的规则单独的对象,你可以把一个列表或字典,但像这样的定义,它不使其成为更具可读性,但它确实增加了一些可验证性的好处,以及添加不同类型规则的可能性,而不会再次弄乱您的清洁列表。

I think your best bet would be to ceate a Dictionary that would map your values to methods. 我认为你最好的选择是停止一个将你的值映射到方法的Dictionary
Now there are several options what could be the key in your dictionary - see below. 现在有几个选项可能是你词典中的关键 - 见下文。

Disclaimer Please also note that a big if statement is only an issue, if it is spread / duplicated across the codebase, and changes frequently - putting things in a dictionary does not really reduce complexity . 免责声明请注意,if if语句只是一个问题,如果它在代码库中传播/复制,并且频繁更改 - 将内容放入字典并不能真正降低复杂性 Having methods in a dictionary also changes the semantics of your code. 在字典中使用方法也会改变代码的语义。 The first question should be - am I changing the mappings at runtime? 第一个问题应该是 - 我是否在运行时更改映射? Should they really be dynamic? 它们真的应该是动态的吗?

  1. Dictionary with key ValueTuple struct you could use syntax (Shape, Color, Material) - this is the easiest one. 使用键值ValueTuple结构的Dictionary可以使用语法(Shape, Color, Material) - 这是最简单的一种。 Please note - not the Tuple class, but ValueTuple struct. 请注意 - 不是 Tuple类,而是ValueTuple结构。
  2. Dictionary with key Item class itself, but then you would need to take care about proper equality comparison in Item . Dictionary与关键Item类本身,但那么你需要在Item 注意正确的相等比较 You could make Item a struct to get that for free (but with slower performance, that comes from equality comparisons in System.ValueType that uses reflection in general case), or leave it as a class (or struct) and implement IEquatable<Item> , Equals and GetHashCode . 您可以使Item成为一个免费获取的结构 (但性能较慢,来自System.ValueType中通常情况下使用反射的相等比较),或者将其保留为类(或结构)并实现IEquatable<Item>EqualsGetHashCode
    Without proper equality comparison, your dictionary lookup will not work (as suggested by @ckuri) 如果没有适当的相等比较,您的字典查找将无法工作(由@ckuri建议)
  3. Use ValueTuple without a dictionary to simply condense your code. 使用不带字典的ValueTuple来简化您的代码。
  4. A variation of the State pattern where you have a shared interface / base class for your handler. 状态模式的 变体 ,其中包含处理程序的共享接口/基类。 Handler is a separate class that contains one action for a specific set of values, like AnyBlueGlassHandler : Handler . Handler是一个单独的类,它包含一组特定值的操作,如AnyBlueGlassHandler : Handler Each handler checks the If condition, and when true, runs the actions. 每个处理程序检查If条件,当为true时,运行操作。 You could then put the handlers in a List<T> and apply them for an item like handlers.Foreach(x=>x.Handle(item)) 然后,您可以将处理程序放在List<T> ,并将它们应用于像handlers.Foreach(x=>x.Handle(item))

The code when Item is the key could look like: Item是键的代码可能如下所示:

    public static class ItemExtensions
    {
    static Dictionary<Item, Action<Item>>
        methods = new Dictionary<Item, Action<Item>>()
        {
            { new Item(Shape.any, Color.blue, Material.glass), x=> { /*do something*/ } }
        };


    public static bool TryApply(this Item item)
    {
        if (methods.TryGetValue(item, out var action))
        {
            action(item);
            return true;
        }
        return false;
    }
}

the code when ValueTuple is the key could look like ValueTuple是键的代码可能是这样的

 public static class ItemExtensionsUsingValueTuple
 {
    static Dictionary<(Shape, Color, Material), Action<Item>>
        methods = new Dictionary<(Shape, Color, Material), Action<Item>>()
        {
            { (Shape.any, Color.blue, Material.glass), x=> { /*do something*/ } }
        };


    public static bool TryApply(this Item item)
    {
        if (methods.TryGetValue((item.ItemShape, item.ItemColor, item.ItemMaterial), out var action))
        {
            action(item);
            return true;
        }
        return false;
    }
}

a more condensed version of your code with ifs, could look like: 使用ifs的代码的更精简版本可能如下所示:

  • declare Key property on your Item class 声明Item类的Key属性
    public (Shape, Color, Material) Key => (ItemShape, ItemColor, ItemMaterial);
  • use more elegant if statement 使用更优雅的if语句
    if ( item.Key == (Shape.any, Color.blue, Material.glass)) { }

Try Dictionary<T> , for instance: 尝试使用Dictionary<T> ,例如:

static Dictionary<Tuple<Shape, Color, Material>, Action> s_Actions = new
  Dictionary<Tuple<Shape, Color, Material>, Action>() {
    {Tuple.Create(Shape.square, Color.blue, Material.glass), () => { ... } },
     ...  
    {Tuple.Create(Shape.any, Color.red, Material.metal), () => { ... } },
     ...
  };

private static void RunAction(MyItem item) {
  Action action;

  // Either exact match or match with Any 
  if (!s_Actions.TryGetValue(Tuple.Create(item.ItemShape, item.ItemColor, item.ItemMaterial), 
                             out action))
    action = s_Actions.FirstOrDefault(pair => pair.Key.Item1 == Color.any &&
                                              pair.Key.Item2 == item.ItemColor &&
                                              pair.Key.Item3 == item.ItemMaterial) 

  // Execute action if it's found
  if (action != null) 
    action();  
}
void RunAction((Shape shape, Color color, Material material) item)
{
    switch(item)
    {
        case var i1 when i1.color == Color.red && i1.shape == Shape.square:
        case var i2 when i2.color == Color.blue:
            // Do action...
            break;
        case var i1 when i1.shape == Shape.trangle && i1.material == Material.metal:
            // Do action...
            break;
        default:
            // Do action...
            break;
    }
}

This solution uses Value Tuples , but primarily uses C#7 pattern matching in switch statements. 此解决方案使用Value Tuples ,但主要在switch语句中使用C#7 模式匹配

I don't think it totally solves your problem cleanly, but I think the multi-line cases gives you extra readability over the if statement approach and it's easy to implement. 我不认为它完全解决了你的问题干净,但我认为多行案例给你提供了超过if语句方法的额外可读性,并且很容易实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM