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

时间:2009-06-03 11:49:34

标签: c# .net properties new-operator

考虑以下示例代码:

// 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; }
    }
}

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

现在,如果必须使用ParcelDelivery策略交付CustomerOrders,我们可以在CustomerOrder类中考虑“ new ”Delivery属性:

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策略,而无需进行投射。

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

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

8 个答案:

答案 0 :(得分:11)

我更喜欢将类型设为通用:

public abstract class Order<TDelivery> where TDelivery : Delivery
{
    public TDelivery Delivery { ... }
    ...
}

public class CustomerOrder : Order<ParcelDelivery>
{
    ...
}

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

CustomerOrder customerOrder = new CustomerOrder();
Order order = customerOrder;
order.Delivery = new NonParcelDelivery(); // Succeeds!

ParcelDelivery delivery = customerOrder.Delivery; // Returns null

哎哟。

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

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

答案 1 :(得分:7)

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

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

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

答案 2 :(得分:5)

您可以使用泛型。

// order (base) class
public abstract class Order<TDeliveryStrategy> 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<ParcelDelivery>
{
    public CustomerOrder()
        : base(new ParcelDelivery())
    { }
}

答案 3 :(得分:3)

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

在您的情况下,您似乎尝试使用方法隐藏作为向C#引入property covariance的方法。但是,这种方法存在问题。

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

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

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

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

答案 4 :(得分:1)

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

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; } 
}

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

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

答案 5 :(得分:1)

考虑这种方法:

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}
    }
}

这还远非完美(您的示例并未显示为什么基类需要了解有关传递策略的信息,并且通过约束更具意义这至少允许您为属性使用相同的名称并获得类型安全。

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

只要有可能,只读字段总是更好。它们使不变性变得清晰。

答案 6 :(得分:1)

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

// 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; }
    }
}

答案 7 :(得分:0)

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