代码之家  ›  专栏  ›  技术社区  ›  oɔɯǝɹ

C#/.Net中“新”属性的利弊?

  •  9
  • oɔɯǝɹ  · 技术社区  · 17 年前

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

    以及以下示例订单类:

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

    当我派生一个新类型的order类时,它将继承DeliveryStrategy类型的Delivery属性。

    刚出现的 '在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; }
        }
    }
    

    你会考虑使用这个模式吗?为什么?

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

    8 回复  |  直到 17 年前
        1
  •  11
  •   Jon Skeet    17 年前

    我更喜欢将类型设置为泛型:

    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

    如果你不想走一般路线,我会引入一个真正的新属性(使用不同的名称)。

        2
  •  7
  •   Thomas Levesque    17 年前

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

    • 返回一个DbConnection.CreateCommand()命令
    • CreateCommand()使用“new”返回SqlCommand来隐藏基本实现。
    • (其他DbConnection实现也这样做)

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

        3
  •  5
  •   TcKs    17 年前

    // 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())
        { }
    }
    
        4
  •  3
  •   LBushkin    17 年前

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

    在您的例子中,看起来您正试图使用方法隐藏作为引入 property covariance 到C#。然而,这种方法存在一些问题。

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

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

    有一种替代泛型的方法。在您的案例中,您需要考虑是否真的想要一个可设置的属性。如果去掉Delivery属性上的setter,那么使用Order类直接引入的问题就会消失。由于使用构造函数参数设置delivery属性,因此可以保证所有订单都具有正确的策略类型。

        5
  •  1
  •   Joseph    17 年前

    您有什么理由需要更改退货类型吗?如果没有,那么我建议将传递属性设置为虚拟,这样它就必须由继承的类来定义:

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

    如果您确实需要更改返回类型,那么我想知道为什么您需要对返回类型进行如此剧烈的行为更改。不管怎样,如果是这样的话,这对你来说是行不通的。

    因此,为了直接回答您的问题,我只会在要求返回类型与基类不同的情况下使用您描述的模式,并且非常谨慎(我会分析我的对象模型,看看是否还有其他我可以先做的事情)。如果不是这样,那么我将使用上面描述的模式。

        6
  •  1
  •   ShuggyCoUk    17 年前

    考虑这种方法:

    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来屏蔽它,应该抛出,因为您的不变量已被违反。

    在可能的情况下,最好使用只读字段。它们清楚地说明了不变性。

        7
  •  1
  •   ZombieDev    16 年前

    您的解决方案并没有达到您认为的效果。它似乎可以工作,但它不会调用您的“新”方法。考虑下面的代码更改,添加一些输出以查看调用哪种方法:

    // 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时,您不会调用新的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; }
        }
    }
    
        8
  •  0
  •   supercat    14 年前

    使用 new 隐藏基类的虚拟成员是个坏主意,因为子派生类型将无法正确重写它们。如果存在要在派生类中隐藏的类成员,则不应将基类成员声明为 abstract 或 virtual ,但应该只调用 protected abstract protected virtual 成员派生类型可以使用调用适当 protected 成员并适当地强制转换结果。

    推荐文章