简体   繁体   English

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

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

Seeing as C# can't switch on a Type (which I gather wasn't added as a special case because is relationships mean that more than one distinct case might 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());
    }
}

With C# 7 , which shipped with Visual Studio 2017 (Release 15.*), you are able to use Types in case statements (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;
}

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 ). C# 中绝对缺乏切换类型(更新:在 C#7 / VS 2017 中支持切换类型 - 请参阅下面的 Zachary Yates 的回答)。 In order to do this without a large if/else if/else statement, you'll need to work with a different structure.为了在没有大的 if/else if/else 语句的情况下执行此操作,您需要使用不同的结构。 I wrote a blog post awhile back detailing how to build a TypeSwitch structure.我写了一篇博文,详细介绍了如何构建 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.简短版本:TypeSwitch 旨在防止冗余转换并提供类似于普通 switch/case 语句的语法。 For example, here is TypeSwitch in action on a standard Windows form event例如,这里是 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
        };
    }
}

One option is to have a dictionary from Type to Action (or some other delegate).一种选择是拥有一个从TypeAction (或其他一些委托)的字典。 Look up the action based on the type, and then execute it.根据类型查找动作,然后执行。 I've used this for factories before now.我以前在工厂使用过这个。

With JaredPar's answer in the back of my head, I wrote a variant of his TypeSwitch class that uses type inference for a nicer syntax:考虑到JaredPar 的回答我编写了他的TypeSwitch类的一个变体,它使用类型推断来获得更好的语法:

class A { string Name { get; } }
class B : A { string LongName { get; } }
class C : A { string FullName { get; } }
class X { public string ToString(IFormatProvider provider); }
class Y { public string GetIdentifier(); }

public string GetName(object value)
{
    string name = null;
    TypeSwitch.On(value)
        .Case((C x) => name = x.FullName)
        .Case((B x) => name = x.LongName)
        .Case((A x) => name = x.Name)
        .Case((X x) => name = x.ToString(CultureInfo.CurrentCulture))
        .Case((Y x) => name = x.GetIdentifier())
        .Default((x) => name = x.ToString());
    return name;
}

Note that the order of the Case() methods is important.请注意, Case()方法的顺序很重要。


Get the full and commented code for my TypeSwitch class .获取我的TypeSwitch类的完整和注释代码 This is a working abbreviated version:这是一个有效的缩写版本:

public static class TypeSwitch
{
    public static Switch<TSource> On<TSource>(TSource value)
    {
        return new Switch<TSource>(value);
    }

    public sealed class Switch<TSource>
    {
        private readonly TSource value;
        private bool handled = false;

        internal Switch(TSource value)
        {
            this.value = value;
        }

        public Switch<TSource> Case<TTarget>(Action<TTarget> action)
            where TTarget : TSource
        {
            if (!this.handled && this.value is TTarget)
            {
                action((TTarget) this.value);
                this.handled = true;
            }
            return this;
        }

        public void Default(Action<TSource> action)
        {
            if (!this.handled)
                action(this.value);
        }
    }
}

You can use pattern matching in C# 7 or above:您可以在 C# 7 或更高版本中使用模式匹配:

switch (foo.GetType())
{
    case var type when type == typeof(Player):
        break;
    case var type when type == typeof(Address):
        break;
    case var type when type == typeof(Department):
        break;
    case var type when type == typeof(ContactType):
        break;
    default:
        break;
}

Create a superclass (S) and make A and B inherit from it.创建一个超类 (S) 并使 A 和 B 继承它。 Then declare an abstract method on S that every subclass needs to implement.然后在 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),使其类型安全,并且您不需要抛出那个丑陋的异常。

Yes, thanks to C# 7 that can be achieved.是的,感谢 C# 7 可以实现。 Here's how it's done (using expression pattern ):这是它的完成方式(使用表达式模式):

switch (o)
{
    case A a:
        a.Hop();
        break;
    case B b:
        b.Skip();
        break;
    case C _: 
        return new ArgumentException("Type C will be supported in the next version");
    default:
        return new ArgumentException("Unexpected type: " + o.GetType());
}

If you were using C# 4, you could make use of the new dynamic functionality to achieve an interesting alternative.如果您使用的是 C# 4,您可以利用新的动态功能来实现一个有趣的替代方案。 I'm not saying this is better, in fact it seems very likely that it would be slower, but it does have a certain elegance to it.我并不是说这更好,事实上它似乎很可能会更慢,但它确实有一定的优雅。

class Thing
{

  void Foo(A a)
  {
     a.Hop();
  }

  void Foo(B b)
  {
     b.Skip();
  }

}

And the usage:以及用法:

object aOrB = Get_AOrB();
Thing t = GetThing();
((dynamic)t).Foo(aorB);

The reason this works is that a C# 4 dynamic method invocation has its overloads resolved at runtime rather than compile time.这样做的原因是 C# 4 动态方法调用在运行时而不是编译时解析其重载。 I wrote a little more about this idea quite recently .最近写了更多关于这个想法的文章。 Again, I would just like to reiterate that this probably performs worse than all the other suggestions, I am offering it simply as a curiosity.再次,我想重申一下,这可能比所有其他建议的表现都差,我只是出于好奇而提供它。

For built-in types, you can use the TypeCode enumeration.对于内置类型,您可以使用 TypeCode 枚举。 Please note that GetType() is kind of slow, but probably not relevant in most situations.请注意 GetType() 有点慢,但在大多数情况下可能不相关。

switch (Type.GetTypeCode(someObject.GetType()))
{
    case TypeCode.Boolean:
        break;
    case TypeCode.Byte:
        break;
    case TypeCode.Char:
        break;
}

For custom types, you can create your own enumeration, and either an interface or a base class with abstract property or method...对于自定义类型,您可以创建自己的枚举,以及具有抽象属性或方法的接口或基类...

Abstract class implementation of property属性的抽象类实现

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes FooType { get; }
}
public class FooFighter : Foo
{
    public override FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Abstract class implementation of method方法的抽象类实现

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public abstract class Foo
{
    public abstract FooTypes GetFooType();
}
public class FooFighter : Foo
{
    public override FooTypes GetFooType() { return FooTypes.FooFighter; }
}

Interface implementation of property属性的接口实现

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
    public FooTypes FooType { get { return FooTypes.FooFighter; } }
}

Interface implementation of method方法的接口实现

public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes GetFooType();
}
public class FooFighter : IFooType
{
    public FooTypes GetFooType() { return FooTypes.FooFighter; }
}

One of my coworkers just told me about this too: This has the advantage that you can use it for literally any type of object, not just ones that you define.我的一位同事也告诉了我这一点:它的优点是您可以将它用于几乎任何类型的对象,而不仅仅是您定义的对象。 It has the disadvantage of being a bit larger and slower.它的缺点是体积更大,速度更慢。

First define a static class like this:首先定义一个静态类,如下所示:

public static class TypeEnumerator
{
    public class TypeEnumeratorException : Exception
    {
        public Type unknownType { get; private set; }
        public TypeEnumeratorException(Type unknownType) : base()
        {
            this.unknownType = unknownType;
        }
    }
    public enum TypeEnumeratorTypes { _int, _string, _Foo, _TcpClient, };
    private static Dictionary<Type, TypeEnumeratorTypes> typeDict;
    static TypeEnumerator()
    {
        typeDict = new Dictionary<Type, TypeEnumeratorTypes>();
        typeDict[typeof(int)] = TypeEnumeratorTypes._int;
        typeDict[typeof(string)] = TypeEnumeratorTypes._string;
        typeDict[typeof(Foo)] = TypeEnumeratorTypes._Foo;
        typeDict[typeof(System.Net.Sockets.TcpClient)] = TypeEnumeratorTypes._TcpClient;
    }
    /// <summary>
    /// Throws NullReferenceException and TypeEnumeratorException</summary>
    /// <exception cref="System.NullReferenceException">NullReferenceException</exception>
    /// <exception cref="MyProject.TypeEnumerator.TypeEnumeratorException">TypeEnumeratorException</exception>
    public static TypeEnumeratorTypes EnumerateType(object theObject)
    {
        try
        {
            return typeDict[theObject.GetType()];
        }
        catch (KeyNotFoundException)
        {
            throw new TypeEnumeratorException(theObject.GetType());
        }
    }
}

And then you can use it like this:然后你可以像这样使用它:

switch (TypeEnumerator.EnumerateType(someObject))
{
    case TypeEnumerator.TypeEnumeratorTypes._int:
        break;
    case TypeEnumerator.TypeEnumeratorTypes._string:
        break;
}

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.到目前为止,大多数答案都没有考虑未来的子类,这可能会导致以后出现非常糟糕的维护问题。

I liked Virtlink's use of implicit typing to make the switch much more readable, but I didn't like that an early-out isn't possible, and that we're doing allocations.我喜欢 Virtlink使用隐式类型来使 switch 更具可读性,但我不喜欢提前退出是不可能的,而且我们正在做分配。 Let's turn up the perf a little.让我们把性能调高一点。

public static class TypeSwitch
{
    public static void On<TV, T1>(TV value, Action<T1> action1)
        where T1 : TV
    {
        if (value is T1) action1((T1)value);
    }

    public static void On<TV, T1, T2>(TV value, Action<T1> action1, Action<T2> action2)
        where T1 : TV where T2 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
    }

    public static void On<TV, T1, T2, T3>(TV value, Action<T1> action1, Action<T2> action2, Action<T3> action3)
        where T1 : TV where T2 : TV where T3 : TV
    {
        if (value is T1) action1((T1)value);
        else if (value is T2) action2((T2)value);
        else if (value is T3) action3((T3)value);
    }

    // ... etc.
}

Well, that makes my fingers hurt.嗯,这让我的手指受伤。 Let's do it in T4:让我们在 T4 中做到:

<#@ template debug="false" hostSpecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ import namespace="System.Linq" #> 
<#@ import namespace="System.IO" #> 
<#
    string GenWarning = "// THIS FILE IS GENERATED FROM " + Path.GetFileName(Host.TemplateFile) + " - ANY HAND EDITS WILL BE LOST!";
    const int MaxCases = 15;
#>
<#=GenWarning#>

using System;

public static class TypeSwitch
{
<# for(int icase = 1; icase <= MaxCases; ++icase) {
    var types = string.Join(", ", Enumerable.Range(1, icase).Select(i => "T" + i));
    var actions = string.Join(", ", Enumerable.Range(1, icase).Select(i => string.Format("Action<T{0}> action{0}", i)));
    var wheres = string.Join(" ", Enumerable.Range(1, icase).Select(i => string.Format("where T{0} : TV", i)));
#>
    <#=GenWarning#>

    public static void On<TV, <#=types#>>(TV value, <#=actions#>)
        <#=wheres#>
    {
        if (value is T1) action1((T1)value);
<# for(int i = 2; i <= icase; ++i) { #>
        else if (value is T<#=i#>) action<#=i#>((T<#=i#>)value);
<#}#>
    }

<#}#>
    <#=GenWarning#>
}

Adjusting Virtlink's example a little:稍微调整一下 Virtlink 的例子:

TypeSwitch.On(operand,
    (C x) => name = x.FullName,
    (B x) => name = x.LongName,
    (A x) => name = x.Name,
    (X x) => name = x.ToString(CultureInfo.CurrentCulture),
    (Y x) => name = x.GetIdentifier(),
    (object x) => name = x.ToString());

Readable and fast.可读且快速。 Now, as everybody keeps pointing out in their answers, and given the nature of this question, order is important in the type matching.现在,正如每个人在他们的答案中不断指出的那样,鉴于这个问题的性质,顺序在类型匹配中很重要。 Therefore:所以:

  • Put leaf types first, base types later.先放叶子类型,后放基本类型。
  • For peer types, put more likely matches first to maximize perf.对于对等类型,将更多可能的匹配放在首位以最大化性能。
  • This implies that there is no need for a special default case.这意味着不需要特殊的默认情况。 Instead, just use the base-most type in the lambda, and put it last.相反,只需使用 lambda 中最基础的类型,并将其放在最后。

C# 8 enhancements of pattern matching made it possible to do it like this. C# 8 对模式匹配的增强使得这样做成为可能。 In some cases it do the job and more concise.在某些情况下,它可以完成工作并且更简洁。

        public Animal Animal { get; set; }
        ...
        var animalName = Animal switch
        {
            Cat cat => "Tom",
            Mouse mouse => "Jerry",
            _ => "unknown"
        };

Given inheritance facilitates an object to be recognized as more than one type, I think a switch could lead to bad ambiguity.鉴于继承有助于将对象识别为多个类型,我认为切换可能会导致严重的歧义。 For example:例如:

Case 1情况1

{
  string s = "a";
  if (s is string) Print("Foo");
  else if (s is object) Print("Bar");
}

Case 2案例二

{
  string s = "a";
  if (s is object) Print("Foo");
  else if (s is string) Print("Bar");
}

Because s is a string and an object.因为 s 是一个字符串一个对象。 I think when you write a switch(foo) you expect foo to match one and only one of the case statements.我认为当您编写switch(foo)您希望 foo 匹配一个且仅一个case语句。 With a switch on types, the order in which you write your case statements could possibly change the result of the whole switch statement.使用 switch 类型,你编写 case 语句的顺序可能会改变整个 switch 语句的结果。 I think that would be wrong.我认为那是错误的。

You could think of a compiler-check on the types of a "typeswitch" statement, checking that the enumerated types do not inherit from each other.您可以考虑对“typeswitch”语句的类型进行编译器检查,检查枚举类型是否相互继承。 That doesn't exist though.但这并不存在。

foo is T is not the same as foo.GetType() == typeof(T) !! foo is Tfoo.GetType() == typeof(T) !!

I would either我要么

Another way would be to define an interface IThing and then implement it in both classes here's the snipet:另一种方法是定义一个接口 IThing 然后在两个类中实现它,这里是 snipet:

public interface IThing
{
    void Move();
}

public class ThingA : IThing
{
    public void Move()
    {
        Hop();
    }

    public void Hop(){  
        //Implementation of Hop 
    }

}

public class ThingA : IThing
{
    public void Move()
    {
        Skip();
    }

    public void Skip(){ 
        //Implementation of Skip    
    }

}

public class Foo
{
    static void Main(String[] args)
    {

    }

    private void Foo(IThing a)
    {
        a.Move();
    }
}

As per C# 7.0 specification, you can declare a local variable scoped in a case of a switch :根据 C# 7.0 规范,您可以在switchcase声明一个局部变量:

object a = "Hello world";
switch (a)
{
    case string myString:
        // The variable 'a' is a string!
        break;
    case int myInt:
        // The variable 'a' is an int!
        break;
    case Foo myFoo:
        // The variable 'a' is of type Foo!
        break;
}

This is the best way to do such a thing because it involves just casting and push-on-the-stack operations, which are the fastest operations an interpreter can run just after bitwise operations and boolean conditions.这是做这种事情的最好方法,因为它只涉及强制转换和堆栈操作,这是解释器在按位操作和boolean条件之后可以运行的最快的操作。

Comparing this to a Dictionary<K, V> , here's much less memory usage: holding a dictionary requires more space in the RAM and some computation more by the CPU for creating two arrays (one for keys and the other for values) and gathering hash codes for the keys to put values to their respective keys.Dictionary<K, V> ,这里的内存使用要少得多:保存字典需要更多的 RAM 空间和 CPU 的更多计算,以创建两个数组(一个用于键,另一个用于值)和收集哈希键的代码将值放入其各自的键。

So, for as far I know, I don't believe that a faster way could exist unless you want to use just an if - then - else block with the is operator as follows:因此,就我所知,我不相信存在更快的方法,除非您只想使用if - then - else块和is运算符,如下所示:

object a = "Hello world";
if (a is string)
{
    // The variable 'a' is a string!
} else if (a is int)
{
    // The variable 'a' is an int!
} // etc.

You can create overloaded methods:您可以创建重载方法:

void Foo(A a) 
{ 
    a.Hop(); 
}

void Foo(B b) 
{ 
    b.Skip(); 
}

void Foo(object o) 
{ 
    throw new ArgumentException("Unexpected type: " + o.GetType()); 
}

And cast the argument to dynamic type in order to bypass static type checking:并将参数强制转换为dynamic类型以绕过静态类型检查:

Foo((dynamic)something);

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);
    }
}

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#).在比较了这里为 F# 功能提供的几个答案的选项后,我发现 F# 对基于类型的切换有更好的支持(尽管我仍然坚持使用 C#)。
You might want to see here and here .你可能想看看这里这里

Create an interface IFooable , then make your A and B classes 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 as instead first checking with is and then casting, as that way you make 2 casts, so it's more expensive.请注意,最好使用as而不是首先检查is然后进行转换,因为这样您可以进行 2 次转换,因此成本更高。

You're looking for Discriminated Unions which are a language feature of F#, but you can achieve a similar effect by using a library I made, called OneOf您正在寻找作为 F# 语言功能的Discriminated Unions ,但是您可以使用我制作的名为 OneOf 的库来实现类似的效果

https://github.com/mcintyre321/OneOf https://github.com/mcintyre321/OneOf

The major advantage over switch (and if and exceptions as control flow ) is that it is compile-time safe - there is no default handler or fall throughswitch (以及ifexceptions as control flow )相比的主要优点是它是编译时安全的 - 没有默认处理程序或失败

void Foo(OneOf<A, B> o)
{
    o.Switch(
        a => a.Hop(),
        b => b.Skip()
    );
}

If you add a third item to o, you'll get a compiler error as you have to add a handler Func inside the switch call.如果您向 o 添加第三项,您将收到编译器错误,因为您必须在 switch 调用中添加处理程序 Func。

You can also do a .Match which returns a value, rather than executes a statement:您还可以执行.Match返回值,而不是执行语句:

double Area(OneOf<Square, Circle> o)
{
    return o.Match(
        square => square.Length * square.Length,
        circle => Math.PI * circle.Radius * circle.Radius
    );
}

With C# 8 onwards you can make it even more concise with the new switch.从 C# 8 开始,您可以使用新开关使其更加简洁。 And with the use of discard option _ you can avoid creating innecesary variables when you don't need them, like this:通过使用丢弃选项 _,您可以避免在不需要时创建不必要的变量,如下所示:

        return document switch {
            Invoice _ => "Is Invoice",
            ShippingList _ => "Is Shipping List",
            _ => "Unknown"
        };

Invoice and ShippingList are classes and document is an object that can be either of them. Invoice 和 ShippingList 是类,而 document 是一个对象,可以是它们中的任何一个。

I would create an interface with whatever name and method name that would make sense for your switch, let's call them respectively: IDoable that tells to implement void Do() .我会创建一个接口,使用对您的 switch 有意义的任何名称和方法名称,让我们分别调用它们: IDoable告诉实现void Do()

public interface IDoable
{
    void Do();
}

public class A : IDoable
{
    public void Hop() 
    {
        // ...
    }

    public void Do()
    {
        Hop();
    }
}

public class B : IDoable
{
    public void Skip() 
    {
        // ...
    }

    public void Do()
    {
        Skip();
    }
}

and change the method as follows:并更改方法如下:

void Foo<T>(T obj)
    where T : IDoable
{
    // ...
    obj.Do();
    // ...
}

At least with that you are safe at the compilation-time and I suspect that performance-wise it's better than checking type at runtime.至少你在编译时是安全的,我怀疑在性能方面它比在运行时检查类型更好。

Should work with应该与

case type _:案例类型_:

like:喜欢:

int i = 1;
bool b = true;
double d = 1.1;
object o = i; // whatever you want

switch (o)
{
    case int _:
        Answer.Content = "You got the int";
        break;
    case double _:
        Answer.Content = "You got the double";
        break;
    case bool _:
        Answer.Content = "You got the bool";
        break;
}

If you know the class you are expecting but you still don't have an object you can even do this:如果你知道你期待的课程,但你仍然没有对象,你甚至可以这样做:

private string GetAcceptButtonText<T>() where T : BaseClass, new()
{
    switch (new T())
    {
        case BaseClassReview _: return "Review";
        case BaseClassValidate _: return "Validate";
        case BaseClassAcknowledge _: return "Acknowledge";
        default: return "Accept";
    }
}

As Pablo suggests, interface approach is almost always the right thing to do to handle this.正如 Pablo 所建议的,接口方法几乎总是处理这个问题的正确做法。 To really utilize switch, another alternative is to have a custom enum denoting your type in your classes.要真正利用 switch,另一种选择是在类中使用一个自定义枚举来表示您的类型。

enum ObjectType { A, B, Default }

interface IIdentifiable
{
    ObjectType Type { get; };
}
class A : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.A; } }
}

class B : IIdentifiable
{
    public ObjectType Type { get { return ObjectType.B; } }
}

void Foo(IIdentifiable o)
{
    switch (o.Type)
    {
        case ObjectType.A:
        case ObjectType.B:
        //......
    }
}

This is kind of implemented in BCL too.这也是在 BCL 中实现的。 One example is MemberInfo.MemberTypes , another is GetTypeCode for primitive types, like:一个例子是MemberInfo.MemberTypes ,另一个是原始类型的GetTypeCode ,例如:

void Foo(object o)
{
    switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
    {
        case TypeCode.Int16:
        case TypeCode.Int32:
        //etc ......
    }
}

I agree with Jon about having a hash of actions to class name.我同意 Jon 关于对类名进行操作的散列。 If you keep your pattern, you might want to consider using the "as" construct instead:如果您保留您的模式,您可能需要考虑使用“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();不同的是,当你使用模式 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.现在也许编译器会优化并且只执行一次 - 但我不会指望它。

This is an alternate answer that mixes contributions from JaredPar and VirtLink answers, with the following constraints:这是一个替代答案,它混合了 JaredPar 和 VirtLink 答案的贡献,具有以下限制:

  • The switch construction behaves as a function , and receives functions as parameters to cases. switch 结构表现为一个函数,并接收函数作为案例的参数
  • Ensures that it is properly built, and there always exists a default function .确保它被正确构建,并且始终存在默认功能
  • It returns after first match (true for JaredPar answer, not true for VirtLink one).在第一场比赛后返回(对于 JaredPar 答案为真,对于 VirtLink 答案为真)。

Usage:用法:

 var result = 
   TSwitch<string>
     .On(val)
     .Case((string x) => "is a string")
     .Case((long x) => "is a long")
     .Default(_ => "what is it?");

Code:代码:

public class TSwitch<TResult>
{
    class CaseInfo<T>
    {
        public Type Target { get; set; }
        public Func<object, T> Func { get; set; }
    }

    private object _source;
    private List<CaseInfo<TResult>> _cases;

    public static TSwitch<TResult> On(object source)
    {
        return new TSwitch<TResult> { 
            _source = source,
            _cases = new List<CaseInfo<TResult>>()
        };
    }

    public TResult Default(Func<object, TResult> defaultFunc)
    {
        var srcType = _source.GetType();
       foreach (var entry in _cases)
            if (entry.Target.IsAssignableFrom(srcType))
                return entry.Func(_source);

        return defaultFunc(_source);
    }

    public TSwitch<TResult> Case<TSource>(Func<TSource, TResult> func)
    {
        _cases.Add(new CaseInfo<TResult>
        {
            Func = x => func((TSource)x),
            Target = typeof(TSource)
        });
        return this;
    }
}

Yes - just use the slightly weirdly named "pattern matching" from C#7 upwards to match on class or structure:是的 - 只需使用从 C#7 向上命名的稍微奇怪的“模式匹配”来匹配类或结构:

IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();

switch (concrete1)
{
    case ObjectImplementation1 c1: return "type 1";         
    case ObjectImplementation2 c2: return "type 2";         
}

I use我用

    public T Store<T>()
    {
        Type t = typeof(T);

        if (t == typeof(CategoryDataStore))
            return (T)DependencyService.Get<IDataStore<ItemCategory>>();
        else
            return default(T);
    }

Try to go that way:尝试走这条路:

public void Test(BaseType @base)
{
    switch (@base)
    {
        case ConcreteType concrete:
            DoSomething(concrete);
            break;

        case AnotherConcrete concrete:
            DoSomething(concrete);
            break;
    }
}

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

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