【问题标题】:Suggestions needed: alternative to overloading "is" and "as" operators in .NET需要的建议:在 .NET 中重载“is”和“as”运算符的替代方法
【发布时间】:2010-12-19 12:38:23
【问题描述】:

我希望这个问题不要被认为太主观 - 我真的不期待一个明确的答案,但我希望每个人的意见至少能帮助我形成自己的意见。

我正在实现一个自定义类型系统,它是经典 OOP 类型系统的超集。在这种类型系统中,对象实例可以在运行时组合以形成新实例,同时保留个体身份。

这段代码:

var p = new Person();
var pa = new Partner(p);

...产生单个组合对象,其中“p”和“pa”是不同的符合 OOP 的视图。 IOW,更改其中一个视图的属性值会立即反映在也包含此属性的任何其他视图上。

这一切都很好,但它缺少两个用于查询类型标识的关键 API。我真的很想能够写出这样的代码:

if (p is Partner)
{
    (p as Partner).SomePartnerProperty = "...";
}

这当然行不通,因为“is”和“as”运算符的行为不能重载/扩展超出 .NET 的 OOP 规则规定的范围。尽管如此,我的类型系统中仍然需要这个功能。

我的第一个想法是使用可以附加到我的类型系统的所有实例的通用扩展方法:

public static bool Is<T>(this BaseType target) where T : BaseType { ... }
public static T As<T>(this BaseType target) where T : BaseType { ... }

忽略不区分大小写语言中的名称冲突问题,就功能而言,这似乎还可以:

if (p.Is<Partner>())
{
    p.As<Partner>().SomePartnerProperty = "...";
}

但是,我不禁想知道 - 这真的是人们能想到的最好、最方便的 API 吗?

您如何建议我实现这两个运算符,以使它们在应用程序代码中使用起来很自然?

更新:对于任何想知道这种类型系统的目的的人......基本上,每种类型都属于以下两个类别之一:Identity角色。在我上面给出的示例中,Person 是一个身份(设计上),而 Partner 是一个角色(同样,设计上 - 它可能设计不同)。这种类型系统的基本规则是任意数量的角色可以由任何给定的身份组成,而身份本身只能由更高的身份组成(例如,一个人可以成为联系人,但永远不能成为公司)。这种类型的系统使应用程序能够透明地处理例如合作伙伴对象,无论他们拥有什么身份(例如个人、公司、银行等)。

【问题讨论】:

  • 在这里野刺,但我想知道你是否可以在 IronPython 中比在 C# 中更容易地做到这一点?您仍然可以在 C# 中编写一堆代码,只需在必要时使用 IronPython。也许了解 OO Python 的人可以加入。
  • 这可以被描述为每个实例(而不是每个类型)的继承吗?此外,当您应用 pa 特定值时,底层 p 对象会发生什么情况?
  • 如果你想让你的库成功,你不能忽略不区分大小写的语言——但好消息是不区分大小写的语言(至少是 VB!)可以工作这样的代码非常好,即使 IsAs 这里的保留字。上下文(即作为方法)使这种用法变得可行。
  • 是的——Python 和 Ruby 都可以实现 mixin 策略。在 Iron* 变体中,如果每个 mixin 都实现了一个实际的 C# 接口,您应该能够将组合对象作为接口的实例公开回 C# 层。
  • @Blixt:可以。不过,我宁愿说组合,因为这里没有真正的“继承”,至少在 OOP 意义上不是。至于你的问题...... p 并不是 pa 的真正基础对象。它们都只是隐藏在场景后面的第三个复合对象的视图。在这方面, p 和 pa 自己不保存 任何 数据 - 它们的属性将所有内容委托给隐藏对象。如果有人说“如果(a == pa)”,答案将是“真”。

标签: c# .net type-systems


【解决方案1】:

我建议使用两个简单的函数。

第一个(至少对我而言)很直观,名称如下:

p.IsType(typeHere)
p.IsPartner()

第二个是简单的To-Call:

p.ToPartner()

我不会将它们实现为泛型(只是觉得不适合我),尤其是最后一个不是。

【讨论】:

  • Bobby,你觉得 Linq 中的 OfType() 方法怎么样?泛型似乎很适合我……
  • 我认为他希望实现一个实际的类型系统,而不是像 PartnerPerson 这样的具体类。我认为这种方法在那种情况下行不通。
  • @Rob:我希望这是通用的(或至少有一个参数(类型))。 ;) 不过,我从未使用过 Linq。
【解决方案2】:

在我看来,Partner不是Person,但Partner 确实Person。对我来说,界面是有意义的

var p = new Person();
var pa = new Partner(p);

pa.SomePartnerProperty = "...";
pa.Person.SomePersonProperty = "...";

【讨论】:

  • 我期待这样的答案。 :) 这是我的设计问题的经典 OOP 解决方案。但这并不理想,我可以解释原因。当 Partner 成为 Person 的属性时(“具有”),这是一种单向关系,不适用于特定域的条款,其中每个 Partner 都被视为个人或公司。在没有某种形式的反向引用的情况下,不可能让应用程序与合作伙伴进行交易并且仍然能够访问下面的人的数据。自定义类型系统解决了这个问题,而不会“一切都必须是一切的属性”。
  • 有趣...我明白你在做什么。我想不出我喜欢的不维护“反向引用”的解决方案
【解决方案3】:

为了保持对转换过程的完全控制(并确保您的代码实际运行),您的方法似乎是最明智的。您可以使用 explicitimplicit 运算符来控制类型转换(通过隐式转换或通过强制转换显式),但这些系统中有足够的“陷阱”,使其成为看起来不太理想的选择就像您正在尝试构建架构一样。

【讨论】:

    【解决方案4】:

    对于 C#,我认为您的“第一个想法”(例如 x.Is&lt;T&gt;())看起来不错(我认为这是我会做的事)。

    如果你走 F# 路线,你可以使用主动模式,例如

    match p with
    | IsPartner pa -> DoPartnerStuff(pa)
    | _ -> JustPersonStuff(p)
    

    【讨论】:

    • 虽然在只有两种替代方案的情况下,F# 模式匹配只是一种奇特的 if/else,但如果完整模型包括多个可能的互斥角色,这种方法会给您更多的“开关”-喜欢风格。
    • 不错。使用提供更大语法灵活性的语言肯定会解决问题。 :) 不幸的是,我已经知道这个 API 的大多数消费者将是 C# 和 VB 群体的成员。
    【解决方案5】:

    只是一些在线头脑风暴......

    class Role {
        public Role(Person p) { p.liRoles.Add(this); }
    }
    class Partner : Role {
        public Partner(Person p) : base(p) {}
    }
    class Person { 
        List<IRole> liRoles;
        public T As<T>() { 
            foreach(IRole r in liRoles){ if r is T return r; }
            return null;
        }
        public bool Is<T>() { return As<T>() != null; }
    }
    var p = new Person();
    var pa = new Partner(p);
    

    这应该允许类似

    if (p is Partner)
       (p as Partner).PartnerMethod();
    

    虽然我没有通过编译器运行它,所以它可能完全错误;)

    【讨论】:

    • 谢谢,但您没有抓住重点。问题不在于 IS 和 AS 的 实现 - 我已经很好地解决了这个问题。我正在寻找的是用于 IS 和 AS 的 API signature。 IOW,如何在应用程序代码中使用它们。在这方面,您的建议与我原来的建议产生的效果相同。它也保留了同样的问题:即轻微滥用泛型方法(Is 中的参数 T 并没有真正用于签名中),这自然会被 FxCop 拾取。
    【解决方案6】:

    等等...我看不出普通的 OO 代码有什么问题(除非你有很多类)

    interface IPartner {...}
    interface IPerson {...}
    interface ICompany {...}
    
    class Person : IPerson {...}
    class PersonPartner : Person, IPartner {...}
    class Company : ICompany {...}
    class CompanyPartner : Company, IPartner {...}
    

    当然,如果使用多重继承,这会更容易......

    【讨论】:

    • 我的类型系统中的实际层次结构导致大量可能的组合。必须为每个组合声明一个类并不是预期的结果。如果是这样,就不需要自定义类型系统。 :)
    【解决方案7】:

    我对问题所在的解读是 C# 的 mixin 系统——正如 Is it possible to implement mixins in C#? 所讨论的那样。底线是,有一些方法可以获得正确的 mixin 语法所允许的大部分内容——尽管我个人从未发现这些工作区可以提供良好的成本/收益结果。

    【讨论】:

    • 你会的!它基本上是 mixins,对有效的组合有一些特殊的限制。感谢您的链接 - 我不知何故错过了那个线程。
    【解决方案8】:

    我最近一直在思考这些概念以及traits in C#。我认为您对类型测试扩展方法的想法并没有比这更好(这几乎正是我想出的)。借用Perl 6 roles 的一些术语,我将您的Is 方法称为Does。但我会保持 As 不变。

    但是,我认为这种用法对于您想要实现的目标来说并不自然:

    var p = new Person(); 
    var pa = new Partner(p); 
    

    看起来它们不会指向同一个实例。现在借用Scala,也许是这样的?

    var p = new Person(); 
    var pa = p.With<Partner>(); 
    

    无论如何,你完成你的 API 了吗?是开源的吗?

    【讨论】:

      猜你喜欢
      • 2011-11-03
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-25
      • 1970-01-01
      相关资源
      最近更新 更多