鉴于c#不能切换类型(我收集到没有作为特殊情况添加,因为关系意味着可能应用多个不同的情况),有没有更好的方法来模拟类型切换?

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语句,您需要使用不同的结构。前段时间我写了一篇博文,详细介绍了如何构建TypeSwitch结构。

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

短版本: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"));

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

其他回答

我将创建一个接口,其名称和方法名称对您的交换机有意义,让我们分别调用它们: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();
    // ...
}

至少这样你在编译时是安全的,而且我认为在性能方面它比在运行时检查类型要好。

创建一个接口IFooable,然后让你的A和B类实现一个公共方法,它反过来调用你想要的相应方法:

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,然后再强制转换,因为这样你会进行两次强制转换,所以成本更高。

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

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

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

你可以创建重载方法:

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

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

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

并将参数转换为动态类型,以绕过静态类型检查:

Foo((dynamic)something);