【问题标题】:C# "Rename" Property in Derived Class派生类中的 C#“重命名”属性
【发布时间】:2012-09-01 18:45:57
【问题描述】:

当你读到这篇文章时,你会非常想给出这样的建议:“这是一个坏主意,原因如下......”

忍受我。我知道还有其他方法可以解决这个问题。这个问题应该被认为是琐事。

假设您有一个“交易”类,它具有所有交易共同的属性,例如发票、采购订单和销售收据。

让我们以交易“金额”为例,它是给定交易中最重要的货币金额。

public class Transaction
{
    public double Amount { get; set; }

    public TxnTypeEnum TransactionType { get; set; }
}

这个金额在派生类型中可能有一个更具体的名称……至少在现实世界中是这样。比如下面的值其实都是一样的:

  • 交易 - 金额
  • 发票 - 小计
  • 采购订单 - 总计
  • 销售收据 - 金额

所以现在我想要一个派生类“发票”,它有一个小计而不是一般命名的金额。理想情况下,以下两种情况都是正确的:

  1. 在 Transaction 实例中,Amount 属性是可见的。
  2. 在 Invoice 实例中,Amount 属性将被隐藏,但 Subtotal 属性将在内部引用它。

发票如下所示:

public class Invoice : Transaction
{
    new private double? Amount
    {
        get
        {
            return base.Amount;
        }
        set
        {
            base.Amount = value;
        }
    }

    // This property should hide the generic property "Amount" on Transaction
    public double? SubTotal
    {
        get
        {
            return Amount;
        }
        set
        {
            Amount = value;
        }
    }

    public double RemainingBalance { get; set; }
}

当然,Transaction.Amount 在任何 Invoice 实例上仍然可见。

感谢观看!

【问题讨论】:

  • 还强烈建议使用小数作为金额类型而不是双精度。

标签: c# properties alias derived-class base-class


【解决方案1】:

感谢大家的帮助。

好的,当派生类是基实例时,您当然不能在基类上“隐藏”公共属性。在我大脑深处的某个地方,我已经知道这一点。呵呵!

通过使用名为 TransactionBase 的第三个类,我最终让语法糖按照我想要的方式为消费者提供服务。这个类是抽象的,它包含所有交易的共享的、非别名的东西,比如货币、汇率、创建/修改的日期和时间、交易日期等......除了有别名的东西,比如金额。

在这里,我只显示有问题的 Amount 属性:

public abstract class TransactionBase
{
    protected virtual double Amount { get; set; }
}

那么事务看起来像这样:

public class Transaction : TransactionBase
{
    public new double Amount 
    { 
        get
        {
            return base.Amount;
        }
        set
        {
            base.Amount = value;
        }
    }
}

和发票:

public class Invoice : TransactionBase
{
    public double SubTotal
    {
        get
        {
            return Amount;
        }
        set
        {
            Amount = value;
        }
    }
}

访问按我想要的方式工作:

var transaction = new Transaction();

// This line works fine:
var transactionAmount = transaction.Amount;



var invoice = new Invoice();

// This line works fine:
var invoiceSubtotal = invoice.SubTotal;

// This line won't compile.
// Error: TransactionBase.Amount is inaccessible due to its protection level.
var invoiceAmount = invoice.Amount;

所以我最初的问题的答案是,“不”你不能隐藏公共继承成员。上面的解决方案通过直接在派生类型中访问受保护的成员来伪造它,但它仍然有点糟糕。打字太多。

当然,既然我摆弄了所有这些,我看到更好的解决方案完全抛弃了受保护的成员并节省了我一些打字时间。顺便说一句,是的,我很尴尬,我没有立即跳到这个解决方案。

编辑:实际上,我的回答中的第一个方法可能会更好。对于第二个,从交易转换为发票时,我会丢失“金额”或“小计”。

public abstract class TransactionBase
{
    // There are some shared properties here.
}

public class Transaction : TransactionBase
{
    public double Amount { get; set; }
}

public class Invoice : TransactionBase
{
    public double SubTotal { get; set; }
}

【讨论】:

    【解决方案2】:

    使用隐式转换运算符怎么样?

    class Program
    {
        static void Main(string[] args)
        {
            var transaction = new Transaction();
            var transactionAmount = transaction.Amount;
    
            var invoice = new Invoice();
            var invoiceSubTotal = invoice.SubTotal;
    
            Transaction fromInvoiceToTrans = invoice;
            var fromInvoiceToTransAmount = fromInvoiceToTrans.Amount;
    
        }
    }
    
    public class Transaction
    {
        public decimal Amount {get; set;}
    }
    
    public class Invoice
    {
        public decimal SubTotal
        {
            get;
            set;
        }
    
        public static implicit operator Transaction(Invoice invoice)
        {
            return new Transaction
            {
                Amount = invoice.SubTotal
            };
        }
    }
    

    【讨论】:

    • 感谢您让我查找隐式运算符。我完全不知道他们。不过,我想我需要一个双向的。感觉它最终可能会成为包含数十种交易类型的大量代码。
    【解决方案3】:

    您正在寻找的确切行为在语法上没有意义。如果Invoice继承了Transaction,那么它是一种事务,编译器要求它继承它的所有属性。

    我认为您正在寻找的一般行为是封装,这可以通过接口来完成。

    public interface IInvoice
    {
        double? Amount { get; set; }
    }
    
    public interface ITransaction
    {
        double? SubTotal { get; set; }
    }
    

    要求代码的使用者使用这些接口,然后对他们隐藏实现细节。


    所以现在,这些类可以按照您想要的方式运行。 SubTotal 对类(发票)仍然可见,但对界面(IInvoice)隐藏。

    public class Transaction : ITransaction
    {
        public double? SubTotal { get; set; }
    }
    
    public class Invoice : IInvoice
    {
        public double? Amount
        {
            get { return base.SubTotal; }
            set { base.SubTotal = value; }
        }
    }
    

    【讨论】:

    • 感谢您的回答。我可以这样做,但我从语法上知道我希望消费者看到什么。实现它的实施确实是有问题的。 - 问题的原因是一场辩论,或者这是否可以通过让一个属性引用一个较低级别的属性而不是通过拥有一个存在于内存中的附加属性来起作用。这个问题的真实案例有数百万个实例。
    • @Eric 你通过将接口返回给消费者来实现它。这样,无论您实际使用什么类/层次结构,他们都只会看到您想要的。
    【解决方案4】:

    我建议在这种情况下使用组合而不是继承。主要原因是 Transaction 的基本实现似乎永远不会以继承的方式使用。您可以将交易隐藏为 Invoice 的受保护或私有成员,并使用 Invoice 的公共属性公开/操作它。

    一个这样的例子可能如下所示:

    public class Invoice
    {
            private readonly Transaction _transaction; 
            public Invoice():this(new Transaction())
            {
            }
            public Invoice(Transaction transaction)
            {
                _transaction = transaction;
            }
    
            // This property should hide the generic property "Amount" on Transaction
            public double? SubTotal
            {
                get
                {
                    return _transaction.Amount;
                }
                set
                {
                    _transaction.Amount = value ?? 0.0f;
                }
            }
    
            public double RemainingBalance { get; set; }
        }
    

    【讨论】:

    • 感谢您的回答。但是,Transaction 将有其他属性需要公开,但在发票上没有别名,现在我们正在做很多额外的输入来公开这些属性。
    【解决方案5】:

    简而言之,你不能这样做。

    总之,您可以通过对接口进行编码来模拟这一点!

    public class Transaction
    {
        public double Amount { get; set; }
    }
    public interface IInvoice
    {
        public double? SubTotal { get; set; }
    }
    public class Invoice : Transaction, IInvoice
    {
        public double? SubTotal
        {
            get
            {
                return Amount;
            }
            set
            {
                Amount = value ?? 0.0f;
            }
        }
    }
    

    【讨论】:

    • +1。此外,如果 Transaction.Amount 受到保护并且接口被明确实现,它将更接近 OP 想要的。
    • 谢谢。你说的是真的,只是我理解他说他希望能够直接实例化 Transaction 并公开使用 Amount。
    • @Alexei Levenkov - 这个答案很接近,我注意到,正如你所做的那样,发票实例仍然会公开 Transaction.Amount,因为它是公开的。但随后对其进行保护将隐藏交易实例上的金额。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-27
    • 1970-01-01
    • 2022-06-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多