C#:避免数据结构的if(x是Foo){…}或者if(x是Bar){…}

C#:避免数据结构的if(x是Foo){…}或者if(x是Bar){…},c#,data-structures,C#,Data Structures,我有一系列数据结构,如: abstract class Base {...} class Foo : Base {...} class Bar : Base {...} 以及一种方法,该方法接受一个基并根据它是哪个子类对其进行转换: void Convert(Base b) { if (b is Foo) // Do the Foo conversion else if (b is Bar) // Do the Bar conversion ... 显然,这是一个

我有一系列数据结构,如:

abstract class Base {...}
class Foo : Base {...}
class Bar : Base {...}
以及一种方法,该方法接受一个基并根据它是哪个子类对其进行转换:

void Convert(Base b) {
  if (b is Foo) 
    // Do the Foo conversion
  else if (b is Bar) 
    // Do the Bar conversion
...
显然,这是一个糟糕的面向对象的方法——Convert方法必须知道基类的每个派生类,并且必须在每次扩展基类时进行更改。解决这个问题的“常规”OO方法是让基类的每个派生类负责转换自身,例如

abstract class Base {
  abstract Converted Convert();
...}
class Foo : Base {
  override Converted Convert(){...}
...}
class Bar : Base {
  override Converted Convert(){...}
...}
然而,在我编写的代码中,Base是一个纯数据结构(只有getter和setter,没有逻辑),它位于另一个程序集中,我无权更改它。有没有一种方法可以更好地构造代码,而不强制基类的派生类具有逻辑


谢谢

如果你那样做很痛,那就不要那样做。根本问题是:

我有一个方法,它以
为基础

既然这是根本问题,就把它解决掉。完全摆脱那种方法,因为它很糟糕

替换为:

void Convert(Foo foo) {
   // Do the Foo conversion
}
void Convert(Bar bar) {
   // Do the Bar conversion
}

现在没有任何方法可以撒谎说它可以转换任何
基,而实际上它不能。

我遇到了类似的情况,并提出了一个类似于函数式语言中模式匹配的解决方案。以下是语法:

注意,在lambdas中,foo和bar是强类型的,作为它们各自的类型;不需要投

var convertedValue=新类型开关(r)
.ForType(foo=>/*进行foo转换*/)
.ForType(bar=>/*执行bar转换*/)
).GetValue();
下面是TypeSwitch类的实现:

public class TypeSwitch<T, TResult>
{
    bool matched;
    T value;
    TResult result;

    public TypeSwitch(T value)
    {
        this.value = value;
    }

    public TypeSwitch<T, TResult> ForType<TSpecific>(Func<TSpecific, TResult> caseFunc) where TSpecific : T
    {
        if (value is TSpecific)
        {
            matched = true;
            result = caseFunc((TSpecific)value);
        }
        return this;
    }

    public TResult GetValue()
    {
        if (!matched)
        {
            throw new InvalidCastException("No case matched");
        }
        return result;
    }
}
公共类类型开关
{
布尔匹配;
T值;
结果结果;
公共类型开关(T值)
{
这个值=值;
}
公共类型开关ForType(Func caseFunc),其中T指定:T
{
如果(值为t特定值)
{
匹配=真;
结果=caseFunc((t特定)值);
}
归还这个;
}
公共TResult GetValue()
{
如果(!匹配)
{
抛出新的InvalidCastException(“无匹配案例”);
}
返回结果;
}
}

我相信它可以被清理,在大多数情况下,埃里克·利珀特是对的,这个前提是有根本缺陷的。然而,如果你遇到这样的情况,你唯一的选择就是一堆
is
和cast,我认为这会更干净一些

访问者模式可能有助于您引入一个从基继承的中间抽象类,Foo和Bar从该类继承。然后向该中间类添加一个抽象的
Convert
方法。当有人将
Base
扩展到一个新类时会发生什么情况?如果不修改
Base
Foo
Bar
类,我不确定是否有好的方法来解决这个问题。我不会批评这个前提。你的问题是“我有一个接受任何B的操作C,但B的作者不能保证B的每一个子类型都能支持操作C”。因此,操作C不接受任何B,它只接受一些B,因此C的作者正在编写一份合同,它无意保留该合同。不要这样做。只有当编译器知道它在编译时是哪种具体类型时,这才有效。@JohnGibb:Correct;正是在这种情况下,根据原始海报呈现的场景,已知代码的类型是正确的。静态分析语言的全部要点是告诉您程序何时没有类型错误。如果程序员不知道对象的类型,那么程序员就不知道
Convert
是合法操作!这肯定是正确的,但是如果用户不能编辑原始类型,那么他们除了枚举可能的子类型之外就别无选择。我只是不明白你的回答对他们有什么帮助;他们甚至必须使用强制转换来调用您的重载@约翰吉布:这个问题的存在在逻辑上并不意味着有一个令人愉快的解决方案。我注意到,Base、Foo和Bar类的开发人员没有充分预测客户的需求。如果他们当时(1)他们会自己提供必要的代码,(2)他们会为更好的扩展性设计类层次结构,或者(3)他们会设计层次结构,以便Foo和Bar是唯一可能的派生基类型。此解决方案只有在Convert是
public
时才有意义,因此,重要的是考虑合同的方法。如果它是
private
,那么您已经知道如何调用该方法,因此契约的重要性要小得多,但您只是将类型检测和强制转换从被调用方移动到调用方,这使得它不如OP所具有的优雅。
public class TypeSwitch<T, TResult>
{
    bool matched;
    T value;
    TResult result;

    public TypeSwitch(T value)
    {
        this.value = value;
    }

    public TypeSwitch<T, TResult> ForType<TSpecific>(Func<TSpecific, TResult> caseFunc) where TSpecific : T
    {
        if (value is TSpecific)
        {
            matched = true;
            result = caseFunc((TSpecific)value);
        }
        return this;
    }

    public TResult GetValue()
    {
        if (!matched)
        {
            throw new InvalidCastException("No case matched");
        }
        return result;
    }
}