C#/ .Net中“新”属性的优缺点?

考虑以下示例代码:

// delivery strategies public abstract class DeliveryStrategy { ... } public class ParcelDelivery : DeliveryStrategy { ... } public class ShippingContainer : DeliveryStrategy { ... } 

和以下示例Order类:

 // order (base) class public abstract class Order { private DeliveryStrategy delivery; protected Order(DeliveryStrategy delivery) { this.delivery = delivery; } public DeliveryStrategy Delivery { get { return delivery; } protected set { delivery = value; } } } 

当我派生出一种新类型的订单类时,它将inheritanceDeliveryStrategy类型的Delivery属性。

现在,当给出必须使用ParcelDelivery策略交付CustomerOrders时,我们可以考虑在CustomerOrder类中“ ”交付属性:

 public class CustomerOrder : Order { public CustomerOrder() : base(new ParcelDelivery()) { } // 'new' Delivery property public new ParcelDelivery Delivery { get { return base.Delivery as ParcelDelivery; } set { base.Delivery = value; } } } 

(CustomerOrder显然需要确保与Order兼容(polymorph))

这允许在CustomerOrder上直接使用ParcelDelivery策略,而无需进行强制转换。

你会考虑使用这种模式吗? 为什么/为什么不呢?

更新 :我提出了这种模式,而不是使用generics,因为我想将它用于多个属性。 我不想对所有这些属性使用generics类型参数

我认为这是一个很好的模式。 通过消除转换结果的需要,它可以更容易地显式使用派生类型,并且它不会“破坏”基类行为。 实际上,在BCL的某些类中使用了类似的模式,例如,查看DbConnection类层次结构:

  • DbConnection.CreateCommand()返回一个DbCommand
  • SqlConnection.CreateCommand()使用’new’隐藏基本实现以返回SqlCommand。
  • (其他DbConnection实现也这样做)

因此,如果您通过DbConnection变量操作连接对象,CreateCommand将返回DbCommand; 如果你通过SqlConnection变量操作它,CreateCommand将返回一个SqlCommand,如果你将它分配给一个SqlCommand变量,则避免使用它。

我更喜欢使类型通用:

 public abstract class Order where TDelivery : Delivery { public TDelivery Delivery { ... } ... } public class CustomerOrder : Order { ... } 

这确保了编译时的类型安全性,而不是将其留待执行时间。 它还可以防止以下情况:

 CustomerOrder customerOrder = new CustomerOrder(); Order order = customerOrder; order.Delivery = new NonParcelDelivery(); // Succeeds! ParcelDelivery delivery = customerOrder.Delivery; // Returns null 

哎哟。

我认为new通常是最后的手段。 它在实现和使用方面引入了额外的复杂性。

如果您不想沿着通用路线走下去,我会介绍一个真正的新房产(名称不同)。

你可以使用generics。

 // order (base) class public abstract class Order where TDeliveryStrategy : DeliveryStrategy { private TDeliveryStrategy delivery; protected Order(TDeliveryStrategy delivery) { this.delivery = delivery; } public TDeliveryStrategy Delivery { get { return delivery; } protected set { delivery = value; } } } public class CustomerOrder : Order { public CustomerOrder() : base(new ParcelDelivery()) { } } 

在我看来,使用’new’关键字隐藏基类中的可写属性是个坏主意。 new关键字允许您在派生类中隐藏基类的成员,而不是覆盖它。 这意味着使用基类引用对这些成员的调用仍然访问基类代码,而不是派生类代码。 C#具有’virtual’关键字,允许派生类实际覆盖实现,而不是简单地隐藏它。 这里有一篇相当不错的文章讨论了这些差异。

在您的情况下,看起来您正在尝试使用方法隐藏作为向C#引入属性协方差的方法。 但是,这种方法存在问题。

通常,拥有基类的价值在于允许代码的用户以多态方式处理类型。 如果有人使用对基类的引用设置Delivery属性,您的实现会发生什么? 如果Delivery属性不是ParcelDelivery的实例,派生类是否会中断? 这些是您需要问自己这种实施选择的问题。

现在,如果基类中的Delivery属性没有提供setter,那么您的情况会略有不同。 基类的用户只能检索属性而不能设置它。 由于您将属性访问路由回基类,因此通过基类访问仍然有效。 但是,如果派生类未被密封,则从其inheritance的类可能会通过将Delivery属性隐藏为自己的版本来引入相同类型的问题。

正如其他一些post已经提到的那样,您可以使用generics作为实现不同Delivery属性类型的方法。 乔恩的例子非常擅长certificate这一点。 如果您需要从CustomerOrder派生并将Delivery属性更改为新类型,则generics方法存在一个问题。

有一种替代仿制药。 你需要考虑你是否真的想要一个可设置的属性。 如果你摆脱了Delivery属性的setter,使用Order类引入的问题就会消失。 由于您使用构造函数参数设置交付属性,因此可以保证所有订单都具有正确的策略类型。

您有什么理由需要更改返回类型吗? 如果没有,那么我建议只将交付属性设置为虚拟,因此必须由inheritance的类来定义:

 public abstract class Order { protected Order(DeliveryStrategy delivery) { Delivery = delivery; } public virtual DeliveryStrategy Delivery { get; protected set; } } public class CustomerOrder : Order { public CustomerOrder() : base(new ParcelDelivery()) { } public DeliveryStrategy Delivery { get; protected set; } } 

如果你确实需要改变返回类型,那么我想知道为什么你需要在返回类型上改变行为。 无论如何,如果是这样,那么这对你不起作用。

所以要直接回答你的问题,我只会使用你所描述的模式,如果要求返回类型与基类不同,并且非常谨慎(我会分析我的对象模型,看看是否还有其他的东西我可以先做)。 如果不是这样,那么我会使用我上面描述的模式。

考虑这种方法:

 public interface IOrder { public DeliveryStrategy Delivery { get; } } // order (base) class public abstract class Order : IOrder { protected readonly DeliveryStrategy delivery; protected Order(DeliveryStrategy delivery) { this.delivery = delivery; } public DeliveryStrategy Delivery { get { return delivery; } } } 

然后用

 public class CustomerOrder : Order { public CustomerOrder() : base(new ParcelDelivery()) { } public ParcelDelivery Delivery { get { return (ParcelDelivery)base.Delivery; } } DeliveryStrategy IOrder.Delivery { get { return base.Delivery} } } 

这仍然远非完美(你的例子没有说明为什么基类需要了解交付策略,并且使用约束通用会更有意义但这至少允许你使用相同的名称对于财产和获得类型安全。

你的例子中的as是毫无意义的,如果某些东西不是正确的类型,你不应该用null掩盖它,你应该抛弃你的不变量。

在可能的情况下,只读字段总是更好。 它们使不变性变得清晰。

您的解决方案没有按照您的想法执行。 它似乎工作,但它没有调用你的“新”方法。 考虑对代码进行以下更改以添加一些输出以查看调用的方法:

 // order (base) class public abstract class Order { private DeliveryStrategy delivery; protected Order(DeliveryStrategy delivery) { this.delivery = delivery; } public DeliveryStrategy Delivery { get { Console.WriteLine("Order"); return delivery; } protected set { delivery = value; } } } public class CustomerOrder : Order { public CustomerOrder() : base(new ParcelDelivery()) { } // 'new' Delivery property public new ParcelDelivery Delivery { get { Console.WriteLine("CustomOrder"); return base.Delivery as ParcelDelivery; } set { base.Delivery = value; } } } 

然后使用以下代码片段实际使用您的CustomOrder类:

 Order o = new CustomerOrder(); var d = o.Delivery; 

会输出“订单”。 新方法特别打破了多态性。 它在CustomOrder上创建一个新的Delivery属性,该属性不是Order基类的一部分。 因此,当您使用CustomOrder时,就好像它是一个Order,您不会调用新的Delivery属性,因为它只存在于CustomOrder上,并且不是Order类的一部分。

您要做的是覆盖不可覆盖的方法。 如果您的意思是要覆盖的属性,请将其设为抽象:

 // order (base) class public abstract class Order { private DeliveryStrategy delivery; protected Order(DeliveryStrategy delivery) { this.delivery = delivery; } public abstract DeliveryStrategy Delivery { get { return delivery; } protected set { delivery = value; } } } public class CustomerOrder : Order { public CustomerOrder() : base(new ParcelDelivery()) { } public override ParcelDelivery Delivery { get { return base.Delivery as ParcelDelivery; } set { base.Delivery = value; } } } 

使用new to shadow基类的虚拟成员是一个坏主意,因为子派生类型将无法正确覆盖它们。 如果存在要在派生类中影响的类成员,则不应将基类成员声明为abstractvirtual ,而应简单地调用protected abstractprotected virtual成员。 派生类型可以使用调用相应protected成员的方法来遮蔽基类方法并适当地转换结果。