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#也不支持这一点。
我是不是遗漏了一些显而易见的东西?有什么想法吗?谢谢
您的工作直接违背了类型系统的设计目的,即使始终可以将派生类型较多的类型分配给派生类型较少的变量。(不管怎样:假设你确实设法阻止了从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)
(