C# 有比这更好的选择来“打开类型”吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/298976/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 22:12:08  来源:igfitidea点击:

Is there a better alternative than this to 'switch on type'?

c#switch-statementsystem.type

提问by xyz

Seeing as C# can't switchon a Type (which I gather wasn't added as a special case because isrelationships mean that more than one distinct casemight apply), is there a better way to simulate switching on type other than this?

看到 C# 不能switch在类型上(我收集的不是作为特殊情况添加的,因为is关系意味着case可能适用多个不同的类型),有没有更好的方法来模拟除此之外的类型的切换?

void Foo(object o)
{
    if (o is A)
    {
        ((A)o).Hop();
    }
    else if (o is B)
    {
        ((B)o).Skip();
    }
    else
    {
        throw new ArgumentException("Unexpected type: " + o.GetType());
    }
}

采纳答案by JaredPar

Switching on types is definitely lacking in C# (UPDATE: in C#7 / VS 2017 switching on types is supported - see Zachary Yates's answer below). In order to do this without a large if/else if/else statement, you'll need to work with a different structure. I wrote a blog post awhile back detailing how to build a TypeSwitch structure.

C# 中绝对缺乏切换类型(更新:在 C#7 / VS 2017 中支持切换类型 -请参阅下面的 Zachary Yates 的回答)。为了在没有大的 if/else if/else 语句的情况下执行此操作,您需要使用不同的结构。我写了一篇博文,详细介绍了如何构建 TypeSwitch 结构。

https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types

https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types

Short version: TypeSwitch is designed to prevent redundant casting and give a syntax that is similar to a normal switch/case statement. For example, here is TypeSwitch in action on a standard Windows form event

简短版本:TypeSwitch 旨在防止冗余转换并提供类似于普通 switch/case 语句的语法。例如,这里是 TypeSwitch 在标准 Windows 窗体事件上的作用

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));

The code for TypeSwitch is actually pretty small and can easily be put into your project.

TypeSwitch 的代码实际上非常小,可以很容易地放入您的项目中。

static class TypeSwitch {
    public class CaseInfo {
        public bool IsDefault { get; set; }
        public Type Target { get; set; }
        public Action<object> Action { get; set; }
    }

    public static void Do(object source, params CaseInfo[] cases) {
        var type = source.GetType();
        foreach (var entry in cases) {
            if (entry.IsDefault || entry.Target.IsAssignableFrom(type)) {
                entry.Action(source);
                break;
            }
        }
    }

    public static CaseInfo Case<T>(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            Target = typeof(T)
        };
    }

    public static CaseInfo Case<T>(Action<T> action) {
        return new CaseInfo() {
            Action = (x) => action((T)x),
            Target = typeof(T)
        };
    }

    public static CaseInfo Default(Action action) {
        return new CaseInfo() {
            Action = x => action(),
            IsDefault = true
        };
    }
}

回答by Jon Skeet

One option is to have a dictionary from Typeto Action(or some other delegate). Look up the action based on the type, and then execute it. I've used this for factories before now.

一种选择是使用Typeto Action(或其他一些委托)的字典。根据类型查找动作,然后执行。我以前在工厂使用过这个。

回答by Pablo Fernandez

Create a superclass (S) and make A and B inherit from it. Then declare an abstract method on S that every subclass needs to implement.

创建一个超类 (S) 并使 A 和 B 继承它。然后在 S 上声明一个每个子类都需要实现的抽象方法。

Doing this the "foo" method can also change its signature to Foo(S o), making it type safe, and you don't need to throw that ugly exception.

这样做“foo”方法还可以将其签名更改为 Foo(S o),使其类型安全,并且您不需要抛出那个丑陋的异常。

回答by Zachary Yates

With C# 7, which shipped with Visual Studio 2017 (Release 15.*), you are able to use Types in casestatements (pattern matching):

使用Visual Studio 2017(版本 15.*)附带的C# 7,您可以在case语句中使用类型(模式匹配):

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

With C# 6, you can use a switch statement with the nameof() operator(thanks @Joey Adams):

在 C# 6 中,您可以使用带有nameof() 运算符的 switch 语句(感谢 @Joey Adams):

switch(o.GetType().Name) {
    case nameof(AType):
        break;
    case nameof(BType):
        break;
}

With C# 5 and earlier, you could use a switch statement, but you'll have to use a magic string containing the type name... which is not particularly refactor friendly (thanks @nukefusion)

使用 C# 5 及更早版本,您可以使用 switch 语句,但您必须使用包含类型名称的魔法字符串......这不是特别适合重构(感谢@nukefusion)

switch(o.GetType().Name) {
  case "AType":
    break;
}

回答by Jonas Kongslund

I would either

我要么

回答by Hallgrim

I such cases I usually end up with a list of predicates and actions. Something along these lines:

在这种情况下,我通常会得到一个谓词和动作列表。沿着这些路线的东西:

class Mine {
    static List<Func<object, bool>> predicates;
    static List<Action<object>> actions;

    static Mine() {
        AddAction<A>(o => o.Hop());
        AddAction<B>(o => o.Skip());
    }

    static void AddAction<T>(Action<T> action) {
        predicates.Add(o => o is T);
        actions.Add(o => action((T)o);
    }

    static void RunAction(object o) {
        for (int i=0; o < predicates.Count; i++) {
            if (predicates[i](o)) {
                actions[i](o);
                break;
            }
        }
    }

    void Foo(object o) {
        RunAction(o);
    }
}

回答by sep332

You should really be overloading your method, not trying to do the disambiguation yourself. Most of the answers so far don't take future subclasses into account, which may lead to really terrible maintenance issues later on.

你真的应该重载你的方法,而不是试图自己消除歧义。到目前为止,大多数答案都没有考虑未来的子类,这可能会导致以后出现非常糟糕的维护问题。

回答by Marc Gravell

After having compared the options a few answers here provided to F# features, I discovered F# to have a way better support for type-based switching (although I'm still sticking to C#).
You might want to see hereand here.

在比较了这里为 F# 功能提供的几个答案的选项后,我发现 F# 对基于类型的切换有更好的支持(尽管我仍然坚持使用 C#)。
你可能想看看这里这里

回答by plinth

I agree with Jon about having a hash of actions to class name. If you keep your pattern, you might want to consider using the "as" construct instead:

我同意 Jon 关于对类名进行操作的散列。如果您保留您的模式,您可能需要考虑使用“as”结构:

A a = o as A;
if (a != null) {
    a.Hop();
    return;
}
B b = o as B;
if (b != null) {
    b.Skip();
    return;
}
throw new ArgumentException("...");

The difference is that when you use the patter if (foo is Bar) { ((Bar)foo).Action(); } you're doing the type casting twice. Now maybe the compiler will optimize and only do that work once - but I wouldn't count on it.

不同的是,当你使用模式 if (foo is Bar) { ((Bar)foo).Action(); 你正在做两次类型转换。现在也许编译器会优化并且只执行一次 - 但我不会指望它。

回答by Sunny Milenov

Create an interface IFooable, then make your Aand Bclasses to implement a common method, which in turn calls the corresponding method you want:

创建一个接口IFooable,然后让你AB类实现一个通用的方法,依次调用你想要的对应方法:

interface IFooable
{
    public void Foo();
}

class A : IFooable
{
    //other methods ...

    public void Foo()
    {
        this.Hop();
    }
}

class B : IFooable
{
    //other methods ...

    public void Foo()
    {
        this.Skip();
    }
}

class ProcessingClass
{
    public void Foo(object o)
    {
        if (o == null)
            throw new NullRefferenceException("Null reference", "o");

        IFooable f = o as IFooable;
        if (f != null)
        {
            f.Foo();
        }
        else
        {
            throw new ArgumentException("Unexpected type: " + o.GetType());
        }
    }
}

Note, that it's better to use asinstead first checking with isand then casting, as that way you make 2 casts, so it's more expensive.

请注意,最好使用as而不是先检查is然后进行转换,因为这样您进行了 2 次转换,因此成本更高。