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


331

看到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());
    }
}

18
出于好奇,为什么不只使用多态呢?

18
@jeyoung封闭的类,在临时情况下不值得
xyz



2
@jeyoung:无法使用多态的一种典型情况是,要切换的类型必须不知道包含该switch语句的代码。一个示例:程序集A包含一组数据对象(不会更改,在规范文档等中定义)。组件BCD各自引用A,并提供各种数据对象从A的转换(例如,序列化/反序列化为某种特定格式)。您要么必须镜像BCD中的整个类层次结构,然后使用工厂,要么您必须...
OR Mapper

Answers:


276

C#中绝对没有打开类型的开关(更新:在C#7 / VS 2017中支持打开类型- 请参见下面的Zachary Yates的回答)。为了在没有较大的if / else if / else语句的情况下执行此操作,您将需要使用其他结构。我不久前写了一篇博客文章,详细介绍了如何构建TypeSwitch结构。

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

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

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

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

26
还可以将“ type == entry.Target”更改为“ entry.Target.IsAssignableFrom(type)”以考虑兼容的类型(例如,子类)。
Mark Cidade

更改了代码以使用“ entry.Target.IsAssignableFrom(type)”,以便支持子类。
马特·豪威尔斯,2012年

3
也许值得注意的一件事是(据我了解),需要最后指定“默认”操作以确保检查所有其他情况。我认为这不是标准交换机的要求-我从未见过有人尝试在底部以外的任何地方尝试植入“默认”。几个故障保护选项可能是命令数组以确保默认值是最后一个(位浪费),或者将默认值弹出到变量之后进行处理foreach(只有在找不到匹配项时才会发生)
musefan 2012年

如果发件人为空怎么办?GetType将引发异常
2014年

有两个建议:通过调用default或引发异常来处理null源,并CaseInfo通过仅检查类型值(如果为null则为default)来摆脱布尔值。
Felix K.

291

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

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

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

在C#5和更早的版本中,可以使用switch语句,但是必须使用包含类型名称的魔术字符串...重构起来不是特别友好(感谢@nukefusion)

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

1
这与大小写typeof(string).Name:...一起使用还是必须与Valuetype一起使用?
Tomer W

3
混淆可以打破它
Konrad Morawski 2014年

6
@nukefusion:也就是说,除非您使用闪亮的new nameof()运算符
Joey Adams

21
我不喜欢这个答案,因为nameof(NamespaceA.ClassC)== nameof(NamespaceB.ClassC)是真实的。
ischas

7
(c#7)如果不需要访问该对象,也可以使用下划线:case UnauthorizedException _:
Assaf S.

101

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


31
小注释:可以进行1:1匹配,但是对于继承和/或接口可能会很痛苦-尤其是不能保证用字典保留顺序时。但是,这仍然是我在少数几个地方做的方法; -p所以+1
马克·格雷夫

@Marc:在这种范例中继承或接口将如何中断?假设键是一个类型,而动作是一个方法,那么据我所知,继承或接口实际上应强制使用Right Thing(TM)。我当然可以通过多次操作和缺乏秩序来理解这一问题。
哈珀·谢尔比

2
我过去曾使用过这种技术,通常在移至IoC容器之前
Chris Canal

4
这种技术在继承和接口方面不可行,因为您需要在要检查的对象与所调用的委托之间一一对应。您应该尝试在字典中找到哪个对象的多个接口?
罗伯特·罗斯尼

5
如果要为此目的专门构建字典,则可以使索引器超载以返回键类型的值,或者如果缺少则返回其超类,如果缺少则返回该超类,依此类推,直到没有剩余。
Erik Forbes,2009年

49

有了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;
}

请注意,Case()方法的顺序很重要。


获取我TypeSwitch班级的完整代码和带注释的代码。这是一个工作的缩写版本:

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

看起来是个不错的解决方案,想看看您还有什么要说的,但是博客已经死了。
Wes Grant

1
该死,你是对的。一个小时以来,我的网络主机出现了一些问题。他们正在努力。我博客上的帖子与此处的答案基本相同,但带有完整源代码的链接。
Daniel AA Pelsmaeker 2012年

1
喜欢如何将一堆if括号简化为一个简单的“功能”开关。干得好!
詹姆斯·怀特

2
您还可以为初始情况添加扩展方法:public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource。这可以让您说value.Case((C x) ...
Joey Adams

1
@JoeyAdams:我结合了您的最后建议以及一些小改进。但是,我保持语法不变。
Daniel AA Pelsmaeker 2015年

14

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

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


真正的布鲁诺,但这个问题并不说明这一点。您可以通过Pablo将其包括在您的答案中。
Dana the Sane

从这个问题来看,我认为A和B足够通用,可以成为A = String; B =例如List <int> ...
bruno conde

13

您可以在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;
}

谢谢你这个!也可以用于检测子类:如果(this.TemplatedParent.GetType()。IsSubclassOf(typeof(RadGridView)))可以更改为:切换为(this.TemplatedParent.GetType())情况,当subRadGridView.IsSubclassOf( typeof(RadGridView)):
Flemming Bonde Kentved

你这样做是不对的。请参阅Serge Intern答案,并了解Liskov替代原理
0xF

8

您确实应该重载您的方法,而不要自己去消歧。到目前为止,大多数答案都没有考虑到将来的子类,这可能会在以后导致真正可怕的维护问题。


3
过载分辨率是静态确定的,因此根本无法使用。
Neutrino 2013年

@Neutrino:问题中没有任何内容指示在编译时不知道类型。如果是这样的话,给定OP的原始代码示例,重载比其他任何选项都有意义。
彼得·杜尼奥

我认为他正在尝试使用'if'或'switch'语句来确定类型的事实很清楚地表明该类型在编译时未知。
中微子

@Neutrino,我记得您,正如Sergey Berezovskiy指出的那样,C#中有一个dynamic关键字,它表示必须动态解决的类型(在运行时,而不是在编译时)。
Davide Cannizzo

8

如果您使用的是C#4,则可以利用新的动态功能来实现一个有趣的替代方案。我并不是说这会更好,实际上,它看起来似乎很慢,但是确实具有一定的优雅。

class Thing
{

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

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

}

以及用法:

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

这样做的原因是C#4动态方法调用在运行时而不是编译时解决了其重载。我最近写了一些关于这个想法的文章。再一次,我想重申一点,这可能比所有其他建议都差,我只是出于好奇而提供。


1
我今天有同样的想法。它比打开类型名称慢大约3倍。当然,相对速度相对较慢(对于60,000,000个呼叫,仅4秒。),并且代码可读性强,这是值得的。
达里尔

8

是的,多亏了可以实现的C#7。这是完成的过程(使用表达式模式):

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

1
+1,请参阅C#7功能文档
Florian Koch

7

对于内置类型,可以使用TypeCode枚举。请注意,GetType()有点慢,但在大多数情况下可能不相关。

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

对于自定义类型,您可以创建自己的枚举,以及具有抽象属性或方法的接口或基类。

属性的抽象类实现

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

方法的抽象类实现

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

属性的接口实现

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

接口实现方法

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

我的一位同事也刚刚告诉了我这一点:它的优点是您可以将其用于几乎任何类型的对象,而不仅仅是您定义的对象。它具有较大和较慢的缺点。

首先定义一个静态类,如下所示:

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

然后您可以像这样使用它:

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

感谢为原始类型添​​加TypeCode()变体,因为即使C#7.0-变体也不适用于这些类型(nameof()显然也不适用)
Ole Albers

6

我喜欢Virtlink 使用隐式类型来提高开关的可读性,但是我不喜欢提前退出是不可能的,并且我们正在做分配。让我们稍微提高一下性能。

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.
}

好吧,那使我的手指受伤。让我们在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#>
}

稍微调整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());

可读且快速。现在,由于每个人都在指出自己的答案,并鉴于此问题的性质,顺序在类型匹配中很重要。因此:

  • 首先放置叶子类型,然后放置基本类型。
  • 对于同伴类型,将更可能的匹配放在第一位以最大化性能。
  • 这意味着不需要特殊的默认情况。相反,只需在lambda中使用最基本的类型,然后将其放在最后。

5

鉴于继承有助于将对象识别为一种以上的类型,我认为切换可能会导致模糊性。例如:

情况1

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

情况二

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

因为s是一个字符串一个对象。我认为,当您编写a时,switch(foo)您期望foo可以匹配一个且只有一个case语句。使用开关类型时,编写case语句的顺序可能会更改整个switch语句的结果。我认为那是错误的。

您可以考虑对“ typeswitch”语句的类型进行编译器检查,检查枚举类型是否不会相互继承。那不存在。

foo is Tfoo.GetType() == typeof(T)!! 不一样!



4

另一种方法是定义接口IThing,然后在两个类中都实现它,这里是片段:

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

4

按C#7.0规范,可以声明的作用域局部变量caseswitch

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

这是执行此操作的最佳方法,因为它仅涉及强制转换和堆栈推入操作,这是解释器仅在按位操作和boolean条件之后即可运行的最快操作。

将其与a相比Dictionary<K, V>,这里的内存使用要少得多:持有字典需要更多的RAM空间,CPU需要更多的计算来创建两个数组(一个用于键,另一个用于值)并收集哈希码以供键放入各自键的值。

所以,据我所知,我不相信,一个更快的方法可能存在的,除非你想使用只是一个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.


3

C#8对模式匹配的增强使得可以像这样进行操作。在某些情况下,它可以胜任工作,并且更加简洁。

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

2

您正在寻找Discriminated UnionsF#的语言功能,但是可以使用我制作的一个名为OneOf的库来达到类似的效果。

https://github.com/mcintyre321/OneOf

switch(和ifexceptions as control flow)相比的主要优点是它在编译时安全-没有默认处理程序或失败

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

如果在o上添加第三项,则会出现编译器错误,因为您必须在switch调用内添加处理程序Func。

您还可以执行.Match返回值的a,而不是执行语句:

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

2

创建一个接口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());
        }
    }
}

请注意,最好先使用aswith进行检查,is然后进行强制转换,因为这样可以进行2次强制转换,因此更昂贵。


2

在这种情况下,我通常以谓词和动作列表结尾。遵循以下原则:

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


1

我将使用对开关有意义的任何名称和方法名称创建一个接口,让我们分别调用它们: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();
    }
}

并如下更改方法:

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

至少在编译时您是安全的,我怀疑从性能角度来看,这比在运行时检查类型要好。


1

从C#8开始,您可以使用新开关使其更加简洁。并且使用丢弃选项_可以避免在不需要时创建不必要的变量,如下所示:

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

Invoice和ShippingList是类,而document是可以是它们两者之一的对象。


0

我同意乔恩(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("...");

区别在于,当您使用模式时,如果(foo是Bar){(((Bar)foo).Action(); }您要进行两次类型转换。现在也许编译器会优化并只执行一次,但是我不会指望它。


1
我真的不喜欢多个退出点(返回),但是如果您要坚持使用此点,请在开头添加“ if(o == null)throw”,因为稍后您将不知道投射是否成功,或者对象为空。
阳光明媚的Milenov

0

正如Pablo所建议的那样,接口方法几乎总是正确的做法。要真正利用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:
        //......
    }
}

这也是在BCL中实现的一种。一个示例是MemberInfo.MemberTypes,另一个示例GetTypeCode用于原始类型,例如:

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

0

这是一个替代答案,它将JaredPar和VirtLink答案的贡献混合在一起,并具有以下约束:

  • 开关构造充当一个函数,并接收作为案例参数的函数
  • 确保它已正确构建,并且始终存在一个默认函数
  • 返回后的第一场比赛(真为JaredPar的答案,不是真实VirtLink一个)。

用法:

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

码:

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

0

是的-只需使用C#7向上的名称稍为怪异的“模式匹配”即可在类或结构上进行匹配:

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

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

0

我用

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

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

0

应该与

案例类型_:

喜欢:

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

0

如果您知道期望的班级,但是仍然没有对象,您甚至可以这样做:

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";
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.