关于c#:有没有比这更好的选择’开启类型’?

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

因为C不能打开一个类型(我收集到它不是作为特殊情况添加的,因为IS-A关系意味着可能会应用多个不同的情况),有没有比这更好的方法来模拟打开类型?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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());
    }
}


打开类型在C中显然是缺乏的(更新:在C 7/vs 2017中,支持打开类型-请参阅下面的Zachary Yates的答案)。为了在不使用大型if/else if/else语句的情况下完成此操作,需要使用不同的结构。我写了一篇博文,详细介绍了如何构建一个类型转换结构。

http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx

简短版本:typeswitch的设计目的是防止多余的强制转换,并提供类似于普通switch/case语句的语法。例如,这里是标准Windows窗体事件上的类型切换操作

1
2
3
4
5
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"));

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
        };
    }
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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,可以使用名为()运算符的switch语句(谢谢@joey adams):

1
2
3
4
5
6
switch(o.GetType().Name) {
    case nameof(AType):
        break;
    case nameof(BType):
        break;
}

对于C 5和更早的版本,您可以使用switch语句,但是您必须使用包含类型名的神奇字符串…这对重构不是特别友好(谢谢@nukefusion)

1
2
3
4
switch(o.GetType().Name) {
  case"AType":
    break;
}


一种选择是拥有从TypeAction的字典(或其他代表)。根据类型查找操作,然后执行它。我以前在工厂里用过这个。


Jaredpar的答案在我的脑后,我写了他的TypeSwitch类的一个变体,它使用类型推断来实现更好的语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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类的完整的注释代码。这是一个有效的缩写版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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);
        }
    }
}


创建一个超类,并使a和b从中继承。然后在S上声明每个子类都需要实现的抽象方法。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Thing
{

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

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

}

用法:

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

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


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


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

1
2
3
4
5
6
7
8
9
switch (Type.GetTypeCode(someObject.GetType()))
{
    case TypeCode.Boolean:
        break;
    case TypeCode.Byte:
        break;
    case TypeCode.Char:
        break;
}

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

属性的抽象类实现

1
2
3
4
5
6
7
8
9
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; } }
}

方法的抽象类实现

1
2
3
4
5
6
7
8
9
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; }
}

属性的接口实现

1
2
3
4
5
6
7
8
9
public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes FooType { get; }
}
public class FooFighter : IFooType
{
    public FooTypes FooType { get { return FooTypes.FooFighter; } }
}

方法的接口实现

1
2
3
4
5
6
7
8
9
public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu };
public interface IFooType
{
    FooTypes GetFooType();
}
public class FooFighter : IFooType
{
    public FooTypes GetFooType() { return FooTypes.FooFighter; }
}

我的一个同事也告诉过我:这有一个优点,你可以把它用于任何类型的物体,而不仅仅是你定义的物体。它的缺点是有点大和慢。

首先定义这样的静态类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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());
        }
    }
}

然后你可以这样使用它:

1
2
3
4
5
6
7
switch (TypeEnumerator.EnumerateType(someObject))
{
    case TypeEnumerator.TypeEnumeratorTypes._int:
        break;
    case TypeEnumerator.TypeEnumeratorTypes._string:
        break;
}


我喜欢virtlink使用隐式类型来提高开关的可读性,但我不喜欢这样的做法:不可能提前退出,而且我们正在进行分配。让我们把表演调高一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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中进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<#@ 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的例子:

1
2
3
4
5
6
7
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中最基本的类型,并将其放在最后。


考虑到继承有助于将对象识别为多个类型,我认为切换可能会导致严重的歧义。例如:

案例1

1
2
3
4
5
{
  string s ="a";
  if (s is string) Print("Foo");
  else if (s is object) Print("Bar");
}

案例2

1
2
3
4
5
{
  string s ="a";
  if (s is object) Print("Foo");
  else if (s is string) Print("Bar");
}

因为S是一个字符串和一个对象。我认为,当你写一个switch(foo)时,你希望foo只匹配一个case语句。对于开关类型,编写case语句的顺序可能会更改整个开关语句的结果。我想那是不对的。

可以考虑使用编译器检查"typeswitch"语句的类型,检查枚举的类型是否互相继承。但这并不存在。

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


是的,感谢C 7,这是可以实现的,以下是它的实现方式(使用表达式模式):

1
2
3
4
5
6
7
8
9
10
11
12
13
        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());
        }


使用C 7和模式匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        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;
        }


我也可以

  • 使用方法重载(就像X0n),或者
  • 使用子类(就像pablo),或者
  • 应用访客模式。

我在这里看了几个选项,反映了F可以做什么。f对基于类型的切换有更好的支持(尽管我仍然坚持使用c-p)。你可能想看看这里和这里。


另一种方法是定义一个接口,然后在两个类中实现它。这是狙击手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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();
    }
}

创建一个接口ifooable,然后使A和B类实现一个公共方法,该方法反过来调用您想要的相应方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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());
   }
}
}

请注意,最好使用"as",而不是先检查"is",然后使用casting,因为这样可以制作2个casts(昂贵)。


可以创建重载方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Foo(A a)
{
   a.Hop();
}

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

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

并使用动态参数类型绕过静态类型检查:

1
Foo((dynamic)something);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Mine {
  static List<Func<object, bool>> predicates;
  static List<Action<object>> actions;

  static Mine() {
    AddAction<A>(o => o.Hop());
    AddAction(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);
  }
}

我将创建一个接口,不管名称和方法名对您的交换机有什么意义,让我们分别调用它们:IDoable,它告诉实现void Do()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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();
    }
}

改变方法如下:

1
2
3
4
5
6
7
void Foo<T>(T obj)
    where T : IDoable
{
    // ...
    obj.Do();
    // ...
}

至少在编译时您是安全的,我怀疑从性能上讲,它比在运行时检查类型要好。


根据C_7.0规范,您可以声明一个在switchcase中有作用域的局部变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
object a ="Hello world";
switch (a)
{
    case string _:
        // The variable 'a' is a string!
        break;
    case int _:
        // The variable 'a' is an int!
        break;
    case Foo _:
        // The variable 'a' is of type Foo!
        break;
}

你是不是在问为什么变量被声明为string _?为什么用下划线?

好吧,C 7.0中引入的另一个特性是,您可以将其命名为从未引用过的变量。所以,不能引用变量_。这在许多场景中都是很好的,比如OP要求的,因为他只想检查类型,而不想得到强制引用。否则,可以重命名该变量,并根据需要将其用作引用。

这是实现这一点的最佳方法,因为它只涉及强制转换和推送堆栈操作,这是解释器在执行位操作和boolean条件后可以运行的最快操作。

Dictionary相比,这里的内存使用量要少得多:保存字典需要更多的内存空间,CPU需要更多的计算来创建两个数组(一个用于键,另一个用于值),并为键收集哈希代码以将值放入各自的键。

因此,据我所知,我不认为存在一种更快的方法,即使你不想使用ifthenelse块和is操作符,如下所示:

1
2
3
4
5
6
7
8
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.

你要找的是Discriminated Unions,它是f的语言特性,但是你可以通过使用我制作的一个库(称为其中一个库)来达到类似的效果。

网址:https://github.com/mcintyre321/oneof

switchifexceptions as control flow相比,它的主要优点是编译时安全—没有默认的处理程序或故障转移

1
2
3
4
5
6
7
void Foo(OneOf<A, B> o)
{
    o.Switch(
        a => a.Hop(),
        b => b.Skip()
    );
}

如果向o中添加第三个项,将得到一个编译器错误,因为必须在switch调用中添加一个处理程序func。

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

1
2
3
4
5
6
7
double Area(OneOf<Square, Circle> o)
{
    return o.Match(
        square => square.Length * square.Length,
        circle => Math.PI * circle.Radius * circle.Radius
    );
}

这是一个备选答案,它混合了来自jaredpar和virtlink答案的贡献,并具有以下限制:

  • 开关结构作为一个函数,接收作为事例参数的函数。
  • 确保它是正确构建的,并且始终存在一个默认函数。
  • 它在第一次匹配后返回(对于jaredpar答案为true,对于virtlink one则不是true)。

用途:

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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;
    }
}

正如Pablo所说,接口方法几乎总是处理这一问题的正确方法。要真正使用switch,另一种选择是使用一个自定义枚举来表示类中的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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,例如:

1
2
3
4
5
6
7
8
9
void Foo(object o)
{
    switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode()
    {
        case TypeCode.Int16:
        case TypeCode.Int32:
        //etc ......
    }
}

是的-只需使用从C 7向上略显奇怪的"模式匹配"来匹配类或结构:

1
2
3
4
5
6
7
8
IObject concrete1 = new ObjectImplementation1();
IObject concrete2 = new ObjectImplementation2();

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


我用

1
2
3
4
5
6
7
8
9
    public T Store<T>()
    {
        Type t = typeof(T);

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

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

1
2
3
4
5
6
7
8
9
10
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";
    }
}

应该与

case type _:

像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}

我同意乔恩对类名的操作进行散列。如果保留您的模式,您可能会考虑改用"as"构造:

1
2
3
4
5
6
7
8
9
10
11
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("...");

区别在于,当使用patter if(foo是bar)((bar)foo).action();时,您要执行两次类型转换。现在,也许编译器会进行优化,只做一次——但我不会指望它。