C#:如何禁止从子类到父类的隐式转换(即继承为composition vs.is)

本文关键字:转换 is vs composition 继承 何禁止 禁止 子类 父类 | 更新日期: 2023-09-27 18:02:07

摘要

假设我有两个C#4.0类,一个继承自另一个:

class ParentKey {}
class ChildKey : ParentKey {}

如果我尝试以下操作,我希望编译器发出错误:

ChildKey c = new ChildKey();
ParentKey p = c; // I want compiler error here!

本质上,我想使用继承来实现可重用性,但我想避免通常伴随它而来的多态行为(或者更具体地说,分配兼容性(。类似于C++私有继承。


示例

特别是,当用作某个容器的键时,我希望避免意外地混合ParentKey和ChildKey(因为它们的GetHashCode((或Equals((实现可能不兼容(。例如:

Dictionary<ParentKey, object> d = new Dictionary<ParentKey, object>();
d.Add(new ChildKey(), new object()); // I want compiler error here!

我尝试了什么

现在,我知道我可以使用组合来完全避免继承,但我希望避免这个解决方案带来的冗长(我的ParentKey可能非常复杂,并且可能有很多层次的继承层次结构(。

另一种解决方案是始终使用定制的IEqualityComparer,或者在传递到容器之前基于ChildKey显式地创建新的ParentKey,但这两种方法都很容易忘记,并且在运行时可能相对难以诊断。

正在尝试使转换显式。。。

class ChildKey : ParentKey {
    public static explicit operator ParentKey(ChildKey c) {
        // ...
    }
}

产生编译器错误CS0553:不允许到基类或从基类进行用户定义的转换

结构继承在这里是理想的(所以当传递给声明为ParentKey的东西时,ChildKey的"end"部分是"cutoff"(,但C#也不支持这一点。

我是不是遗漏了一些显而易见的东西?有什么想法吗?谢谢

C#:如何禁止从子类到父类的隐式转换(即继承为composition vs.is)

您的工作直接违背了类型系统的设计目的,即使始终可以将派生类型较多的类型分配给派生类型较少的变量。(不管怎样:假设你确实设法阻止了从Derived到Base的隐式引用转换——是什么阻止了你将Derived转换为object,然后显式地将object转换为Base?在编译时禁止我们在运行时无法阻止的东西似乎是反常的。(

我同意,从语言设计的角度来看,可以创建一种语言,避免通过继承将代码重用与子类型多态性混为一谈。然而,我们在很久很久以前就选择将这两件事混为一谈。你要么接受这种选择,要么使用一种不同的语言来提供你想要的功能。(*(

我的建议是:不要向风中吐痰。要么使用composition,要么精心设计Equals和GetHashCode方法,以便每个人都能很好地合作。

(话虽如此,我经常和你分享你的沮丧,因为通过合成的重用有太多冗长的"仪式"。如果我们能找到一种方法来降低合成的句法负担,那就太好了。(


(*(我绝对不是埃菲尔铁塔的专家;也就是说,在我看来,你的想法就像是不符合继承的Eiffel概念。也许埃菲尔铁塔的专家会对此发表评论?

怎么样:

class BaseKey
{
    // all functionality here
}
class ParentKey : BaseKey
{}
class ChildKey : BaseKey
{}

public struct Exclusive<T>
{
   public Exclusive(T item)
   {
     if (c.GetType () != typeof(ParentKey))
       throw new Exception (); // I want compiler error here!
     Item = item;
   }
   public T Item{get; private set;}
   // todo: add implicit cast to T
   // todo: add forcing non-null to get_Item
}

这是C#(和C++(语言的一个缺点,我希望它能被修复(但对此表示怀疑。(

在我(不常见(的观点中,多态性是一种反模式,基本上是一个以某种方式流行起来的坏主意,你写的功能代码越多,这一点就越明显。

另一方面,扩展是一种非常宝贵的编码机制,试图通过组合来复制它以避免多态性是乏味的、容易出错的,而且无法扩展。

(作为补充说明,IMO不难将其添加到语言中,即"扩展"关键字class ChildKey extends ParentKey { ... }等(

我所做的是使用";奇怪地重复出现的模板模式";

abstract class KeyBase<TDerived> { /* common functionality here */ }
class Key : KeyBase<Key> { /* inherit common functionality */ }

当然,这并不能阻止用户进行

Key c = new Key();
KeyBase<Key> p = c;

但它有几个优点:

  • 从模板论点中可以更明确地看出,意图是扩展,不应声明KeyBase<Key>变量
  • KeyBase方法现在可以接受并返回TDerived,这在处理不可变类时尤其有用
  • 您可以轻松创建不同的Key类型(KeyEx(,这些类型具有相同的API,但类型不同(typeof(Key)!=typeof(KeyEx)(