使用Case/Switch和GetType来确定对象

194

可能是重复问题:
C# - 有没有比“基于类型switch”的更好的替代方法?

如果您想要在对象类型上进行 switch,最佳方式是什么?

代码片段

private int GetNodeType(NodeDTO node)
{
    switch (node.GetType())
    { 
        case typeof(CasusNodeDTO):
            return 1;
        case typeof(BucketNodeDTO):
            return 3;
        case typeof(BranchNodeDTO):
            return 0;
        case typeof(LeafNodeDTO):
            return 2;
        default:
            return -1;
    }
}

我知道这样不行,但是我想知道如何解决。 在这种情况下,if/else语句是否合适?

还是你使用switch并将.ToString()添加到类型中?


20
如果有人感兴趣,可以查看Peter Hallam在http://blogs.msdn.com/peterhal/archive/2005/07/05/435760.aspx上讨论C#不支持此功能的原因。 - sourcenouveau
2
我知道现在已经是2017年了,这是一条旧评论,然而...刚刚读完Peter Hallam的那篇文章,我现在感到困惑。C#7允许进行switch语句,其中case语句的顺序很重要-这似乎与他认为它没有被添加到语言中的主要原因之一相矛盾? - Wayne Feltham
13
实际上,您可以在C# 7中使用类型开关...我猜他们在12年后改变了主意(或者找到了更好的方法来做):https://dev59.com/qHVC5IYBdhLWcg3wbgdS#299001 - drojf
1
相关备注:VB.NET已经内置了这个功能。 - Mike
是的,看起来应该可以工作。你会认为typeof()应该在编译时解析并因此产生一个常量供运行时切换,但不幸的是还没有。至少目前还没有。 :( - Zeek2
十二年后,这是一个有趣的帖子,几乎只有历史意义,除非有人坚持使用 C# < 7。我试图将标签从 C# 更改为 C# 6.0,但我无法做到。 - Marcelo Scofano Diniz
10个回答

182

这并不能直接解决你要切换自己定义的类型的问题, 但是为了帮助那些只想切换内置类型的人,你可以使用TypeCode枚举:

switch (Type.GetTypeCode(node.GetType()))
{
    case TypeCode.Decimal:
        // Handle Decimal
        break;

    case TypeCode.Int32:
        // Handle Int32
        break;
     ...
}

3
好的想法,但似乎对用户定义的类不起作用。 - Samik R
1
不,其他所有东西都只会返回“Object”。 - Ashley
@splattne - 只是好奇,为什么需要编辑缩进? - Ashley
@Ashley 我修复了代码片段,因为“...”不是代码块的一部分。请参见:http://imgur.com/CfTIzTU - 缩进的修复是一个副产品。 :-) - splattne
4
“...” 不是代码,所以它不应该是代码的一部分。虽然我可以理解为了易读性而添加“...”。但是缩进是否正确,取决于个人偏好。我并没有看到StackOverflow有规定如何缩进代码。在这个问题中,缩进风格也是五花八门的。因此,把缩进改为自己喜欢的方式并不能算是“修复”。 - Ashley
显示剩余5条评论

97

如果我非得在不同类型的对象之间做选择,我会使用 .ToString()。然而,我会尽可能避免它: IDictionary<Type, int> 可以更好地完成任务,访问者 可能过于复杂,但否则仍然是一个完全可行的解决方案。


在我看来,IDictionary是一个不错的解决方案。如果需要测试的类型超过一两个,我通常会使用它。或者一开始就使用多态性来避免对类型进行切换。 - OregonGhost
如果在序列化时使用了这个“类型”,那么你会混淆关注点,因此需要考虑多态性。 - Dave Van den Eynde
13
为什么不花点功夫并给出一个在所述情况下应用IDictionary的例子呢? - jasie

49
在MSDN博客文章很多问题:switch on type中,提供了关于为什么.NET不提供类型切换的一些信息。
通常情况下,总会有解决方法。
这个解决方法不是我想出来的,但不幸的是我已经丢失了来源。它可以实现类型切换,但我个人认为它相当笨拙(使用字典的想法更好)。
  public class Switch
  {
      public Switch(Object o)
      {
          Object = o;
      }

      public Object Object { get; private set; }
  }


  /// <summary>
  /// Extensions, because otherwise casing fails on Switch==null
  /// </summary>
  public static class SwitchExtensions
  {
      public static Switch Case<T>(this Switch s, Action<T> a)
            where T : class
      {
          return Case(s, o => true, a, false);
      }

      public static Switch Case<T>(this Switch s, Action<T> a,
           bool fallThrough) where T : class
      {
          return Case(s, o => true, a, fallThrough);
      }

      public static Switch Case<T>(this Switch s,
          Func<T, bool> c, Action<T> a) where T : class
      {
          return Case(s, c, a, false);
      }

      public static Switch Case<T>(this Switch s,
          Func<T, bool> c, Action<T> a, bool fallThrough) where T : class
      {
          if (s == null)
          {
              return null;
          }

          T t = s.Object as T;
          if (t != null)
          {
              if (c(t))
              {
                  a(t);
                  return fallThrough ? s : null;
              }
          }

          return s;
      }
  }

使用方法:

 new Switch(foo)
     .Case<Fizz>
         (action => { doingSomething = FirstMethodCall(); })
     .Case<Buzz>
         (action => { return false; })

8
虽然这是一个相对昂贵的模式,会导致较长的垃圾回收时间,但它看起来还是很不错的。阅读起来也非常方便。 - JoeGeeky
1
这篇文章指出:“程序员们会非常惊讶地发现重新排列case标签会影响选择哪个case。”我完全不同意。想象一下,将燃料表涂成绿色/橙色/红色,你可以使用switch percentageFuelRemaining,然后case > 75case > 50case > 25 - NibblyPig
这是一个很酷的解决方案,但我只会在程序流程中偶尔使用它,反射操作是很耗费资源的。这个方案非常适合处理多个异常和报告错误等,但如果您需要在程序中使用它数百次,那么它就不是好的选择了。 - rollsch

35

我遇到了同样的问题,并看到了这篇帖子。 这是指IDictionary方法吗:

Dictionary<Type, int> typeDict = new Dictionary<Type, int>
{
    {typeof(int),0},
    {typeof(string),1},
    {typeof(MyClass),2}
};

void Foo(object o)
{
    switch (typeDict[o.GetType()])
    {
        case 0:
            Print("I'm a number.");
            break;
        case 1:
            Print("I'm a text.");
            break;
        case 2:
            Print("I'm classy.");
            break;
        default:
            break;
    }
}

如果是这样,我不能说我喜欢用字典中的数字来和case语句匹配。

虽然这样会更理想,但是字典的引用破坏了它:

void FantasyFoo(object o)
{
    switch (typeDict[o.GetType()])
    {
        case typeDict[typeof(int)]:
            Print("I'm a number.");
            break;
        case typeDict[typeof(string)]:
            Print("I'm a text.");
            break;
        case typeDict[typeof(MyClass)]:
            Print("I'm classy.");
            break;
        default:
            break;
    }
}

我是否忽略了另一个实现?


3
或许你可以创建一个枚举来代替类型字典中的int?这样就可以避免在代码中使用那些讨厌的魔数了。 - trod

31

我会使用一个if语句。在这种情况下:

Type nodeType = node.GetType();
if (nodeType == typeof(CasusNodeDTO))
{
}
else ... 
这个问题的另一种解决方法是:
if (node is CasusNodeDTO)
{
}
else ...

第一个例子仅适用于准确类型,而后者还检查继承关系。


我同意这个观点,但是我认为比起重复转换类型的尝试,比较引用更快。 - Dave Van den Eynde
我不确定它是否在比较引用。我认为这涉及到 RuntimeType 系统。不过这只是我的猜测,因为如果不是这样的话,编译器就不会告诉你 typeof(X) 不是常量。 - David Wengier
3
第二种类型检查使用了整个类层次结构,所以速度较慢。 - msfanboy

21
您可以这样做:
function void PrintType(Type t) {
 var t = true;
 new Dictionary<Type, Action>{
   {typeof(bool), () => Console.WriteLine("bool")},
   {typeof(int),  () => Console.WriteLine("int")}
 }[t.GetType()]();
}

这很清晰也很简单。 虽然它比在某个地方缓存字典要慢一些..但对于大量代码来说,这并不重要。


2
有人愿意评论一下为什么这个被踩了吗?它哪里不正确或者表现不佳呢? - Norman H
我不认为我会这样做,但只是为了美学原因(有点愚蠢)。话虽如此,我喜欢看到人们跳出传统思维的框框,这是使用lambda表达式的很酷的方式 :) - LOAS
5
这是一种优雅的解决方案,适用于大量类型,并且可以清晰地传达作者的意图。 - Rupert Rawnsley
这是目前为止针对这个问题给出的最干净的解决方案。 - Vasil Popov
一个有点天真但简洁的解决方案。如果我是楼主,我会接受这个答案,因为...嗯...我喜欢lambda表达式 :P - gvdm

14

您可以这样做:

if (node is CasusNodeDTO)
{
    ...
}
else if (node is BucketNodeDTO)
{
    ...
}
...

虽然这样会更加优雅,但可能不如其他回答的效率高。


经过一些性能测试后,我完全同意使用if else是这些类型检查的最佳选择,使用连续的方法调用非常糟糕,因为即使在很早就找到匹配项(除非抛出异常来停止其他方法的调用,但仍然非常糟糕)。 - Ahmed Fwela
再加上新的C#语法,允许您使用“is”关键字进行强制类型转换,这就是最好的答案。 - knocte

7

一种方法是在NodeDTO中添加一个纯虚的GetNodeType()方法,并在子类中重写它,以便每个子类返回实际类型。


虽然这是面向对象的处理方式,但您可能会决定Node不必支持其中任何一项。 - Dave Van den Eynde
这里大力点赞,也感谢Jason Coyne。难道没有其他人读过《重构》这本书吗?这是一个教科书式的例子:http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html - TrueWill

5

根据您在switch语句中的操作,正确的答案是多态性。只需在接口/基类中放置虚函数并为每个节点类型进行覆盖即可。


1

我实际上更喜欢这里给出的答案: Is there a better alternative than this to 'switch on type'?

然而,在像C#这样的面向对象语言中,不实现任何类型比较方法是有道理的。您可以选择使用继承来扩展和添加所需的额外功能。

这一点在作者博客的评论中进行了讨论: http://blogs.msdn.com/b/jaredpar/archive/2008/05/16/switching-on-types.aspx#8553535

我发现这是一个非常有趣的观点,改变了我在类似情况下的做法,希望这能帮助其他人。

此致
敬礼,
韦恩


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接