【问题标题】:C# How to set non-user-inputted variable values/instances for an object (multiple classes)C#如何为一个对象(多个类)设置非用户输入的变量值/实例
【发布时间】:2017-03-20 18:07:03
【问题描述】:

我正在编写一个简单的练习程序,它按预期工作。然而,它让我意识到我对一个我想更彻底理解的重要领域感到模糊。我创建了一个对象(或实例?)aPay。该程序要求用户输入三个变量的值(在 Main(Program) 类中)。这些值/实例被分配给 aPay。 aPay.WorkerName、aPay.HoursWorked 和 aPay.RateOfPay。我有许多具有计算值的常量和变量,它们在 Pay 类中实例化/计算。这些是非用户输入的值。我想将其中每一个的值发送到对象 aPay,类似于对用户输入值所做的操作。我知道在这个程序中它不是必需的,但是它在更复杂的程序中很有用,我想了解如何实现这一点。我不想输出更多内容或改变程序的功能,我只想将变量(GrossPay、NetPay、FicaTax、FedTax、StateTax 和 HealthIns)的值分配给 aPay 对象。 (我不确定我是否会使用变量(grossPay、netPay、fedTax、ficaTax 等,而不是我刚刚列出的属性)。

我知道我可以通过 Pay aPay = new Pay(workerName, hoursWorked, rateOfPay, GrossPay, netPay, fedTax, ficaTax, stateTax, healthIns) 来做到这一点

或类似的东西?

aPay = { workerName, hoursWorked, rateOfPay, GrossPay, etc } ????

谁能解释我如何才能实现这一目标并专门使用我的代码提供示例代码?我将在下面列出代码:

class MainClass
{
    public static void Main(string[] args)
    {


        Header();
        Directions();

        Pay aPay = new Pay();
        Write("**********************************\n");
        Write("Enter name:  ");
        aPay.WorkerName = ReadLine();

        Write("Enter hours: ");
        aPay.HoursWorked = double.Parse(ReadLine());

        Write("Enter rate:  ");
        aPay.RateOfPay = double.Parse(ReadLine());
        Write("**********************************\n");

        //
        //
        //
        //
        //

        WriteLine(aPay.ToString());

        ReadLine();
    }

    private static void Header()
    {
        WriteLine("*************************************************************");
        WriteLine("\t Pay");
        WriteLine("\t Calculate Net Pay");
        WriteLine("\t Matt Craig");
        WriteLine("\t " + DateTime.Today);
        WriteLine("*************************************************************");
    }

    private static void Directions()
    {
        WriteLine("This program will determine pay.");
        WriteLine(" ");
        WriteLine("You will be asked to enter hours worked" 
                  + "\n and rate of pay.");
        WriteLine(" ");
        WriteLine("*************************************************************");
    }
}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /p>

public class Pay
{
    private string workerName;
    private double hoursWorked;
    private double rateOfPay;
    private double grossPay;
    private double netPay;
    private double FICA_TAX = 0.0765;
    private double FED_TAX = 0.20;
    private double STATE_TAX = 0.10;
    private double HEALTH_INS = 0.07;
    private double ficaTax;
    private double fedTax;
    private double stateTax;
    private double healthIns;

    public Pay()
    {

    }

    public string WorkerName
    {
        set
        {
            workerName = value;
        }
    }

    public double HoursWorked
    {
        set
        {
            hoursWorked = value;
        }
    }

    public double RateOfPay
    {
        set
        {
            rateOfPay = value;
        }
    }

    private double GrossPay
    {
        get
        {
            grossPay = Math.Round((hoursWorked * rateOfPay), 2, MidpointRounding.AwayFromZero);
            return grossPay;
        }
    }

    private double NetPay
    {
        get
        {
            netPay = Math.Round((grossPay - ficaTax - fedTax - stateTax - healthIns), 2, MidpointRounding.AwayFromZero);
            return netPay;
        }
    }

    private double FicaTax
    {
        get
        {
            ficaTax = Math.Round((FICA_TAX * grossPay), 2, MidpointRounding.AwayFromZero);
            return ficaTax;
        }
    }

    private double FedTax
    {
        get
        {
            fedTax = Math.Round((FED_TAX * grossPay), 2, MidpointRounding.AwayFromZero);
            return fedTax;
        }
    }

    private double StateTax
    {
        get
        {
            stateTax = Math.Round((STATE_TAX * grossPay), 2, MidpointRounding.AwayFromZero);
            return stateTax;
        }
    }

    private double HealthIns
    {
        get
        {
            healthIns = Math.Round((HEALTH_INS * grossPay), 2, MidpointRounding.AwayFromZero);
            return healthIns;
        }
    }

    public override string ToString()
    {
        string stats;
        stats = string.Format("Name\t\t  {0} \n", workerName);
        stats += string.Format("Gross Pay\t  {0:c} \n", GrossPay);
        stats += string.Format("FICA tax\t  {0:c} \n", FicaTax);
        stats += string.Format("Federal tax\t  {0:c} \n", FedTax);
        stats += string.Format("State tax\t  {0:c} \n", StateTax);
        stats += string.Format("Health Insurance  {0:c} \n", HealthIns);
        stats += string.Format("Net pay\t\t  {0:c} \n", NetPay);
        return stats;
    }
}

【问题讨论】:

  • 类的构造函数不就是为了这个目的吗?
  • 创建一个实例并设置其属性:aPay.WorkerName = this.workerName; 等。带有所有问号的第二个代码 sn-p 应如下所示:var aPay = new Pay { WorkerName = this.workerName, HoursWorked = this.hoursWorked /* etc. */ };。你的版本看起来你可能已经做了一些 JavaScript; C# 语法在几个方面有所不同。
  • @Pankaj Kapare 分配是专门使用属性过程,而不是使用构造函数。但是我正在尝试的不是必要的,或者我不相信任务的重点。构造函数可能是实现这一目标的好方法。

标签: c# class variables object instance


【解决方案1】:

我相信您要描述的内容是使用 constructors 实现的。让我们看看你拥有的代码。您有一个具有一组属性的类Pay。为简单起见,我假设只有三个:WorkerNameHoursWorkedRateOfPay

让我们从描述这些属性的类开始:

public class Pay
{
    public string WorkerName { get; set; }
    public double HoursWorked { get; set; }
    public double RateOfPay { get; set; }
}

上面的类定义有很多我认为“错误”的地方,但让我们一次解决一个问题。

首先,我们需要计算健康保险费用。它取决于健康保险乘数 (0.7),在我们的例子中是一个静态值。让我们将它添加到一个类中:

public class Pay
{
    private static double HEALTH_INS = 0.07;

    public string WorkerName { get; set; }
    public double HoursWorked { get; set; }
    public double RateOfPay { get; set; }

    private double HealthIns
    {
        get
        {
            return Math.Round((HEALTH_INS * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
        }
    }

    // With latest versions of C# you could actually do this:
    private double HealthInsAlt =>  Math.Round((HEALTH_INS * grossPay), 2, MidpointRounding.AwayFromZero);
}

现在,让我们谈谈您的方案:您想分配这些值。首先,如果该值是相关的并且仅在类中使用(在这种情况下为Pay) - 做我上面描述的完全没问题,我个人更喜欢这种方式 - 很容易阅读代码和了解价值的来源。

但有时(更复杂的场景)它只是不起作用。让我们介绍一个这样的业务案例:健康保险乘数现在是动态的,取决于工资率并且来自数据库。为简化起见,我假设我们可以通过使用某种存储库来获取它:InsuranceMultipliersRepository.GetHealthMultiplier(RateOfPay)

我们可以在创建具有指定类的对象的新实例时调用的构造函数中检索值。让我们实现它:

public class Pay
{
    private static double HEALTH_INS { get; }

    public string WorkerName { get; set; }
    public double HoursWorked { get; set; }
    public double RateOfPay { get; set; }

    private double HealthIns
    {
        get
        {
            return Math.Round((HEALTH_INS * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
        }
    }

    public Pay() {
    // This is a parameterless constructor. C# compiler actually generates this for you behind the scenes and sets every value to the default for its type. We're basically saying "use this instead of default one when creating an object" and providing additional instructions. In our case, the instruction would be retrieving the multiplier from the repository and storing it within the Pay class property.

        var insuranceRepo = new InsuranceMultipliersRepository();

        this.HEALTH_INS = insuranceRepo.GetHealthMultiplier(RateOfPay);
    }
}

现在,这里有一个严重的缺陷:我们还没有RateOfPay 集!这意味着我们需要修改构造函数以确保在创建Pay 对象的新实例时知道RateOfPay 值。让我们开始吧:

public class Pay
{
    private static double HEALTH_INS { get; }

    public string WorkerName { get; set; }
    public double HoursWorked { get; set; }
    public double RateOfPay { get; set; }

    private double HealthIns
    {
        get
        {
            return Math.Round((HEALTH_INS * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
        }
    }

    public Pay(double rateOfPay) {
        var insuranceRepo = new InsuranceMultipliersRepository();
        this.RateOfPay = rateOfPay;
        this.HEALTH_INS = insuranceRepo.GetHealthMultiplier(rateOfPay);
    }
}

好的,现在看起来好多了。但是通过将构造函数更改为参数化,我们有效地使以下代码无效:var somePay = new Pay();。 C# 编译器不会为类实现生成默认的无参数构造函数(在某些情况下它仍然会这样做,例如抽象类)。

值得一提的几件事:您可以看到HEALTH_INS 没有set 方法,但我们仍在设置值。如何?现实情况是,我们可以在构造函数中设置 readonly 属性或没有 setter 的属性(使用 C#6+)。如果您使用的是 C#5 或更早版本,则必须创建一个支持属性(对于公共属性,如果您有一个私有属性,则只需将 setter 留在那里是安全的,反正没人能看到它 ),如果我们需要将保险乘数公开且可读,则类似这样:

public class Pay
{
    private static double _HEALTH_INS { get; set; }

    public double HEALTH_INS { get { return _HEALTH_INS; } }
    public string WorkerName { get; set; }
    public double HoursWorked { get; set; }
    public double RateOfPay { get; set; }

    private double HealthIns
    {
        get
        {
            return Math.Round((HEALTH_INS * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
        }
    }

    public Pay(double rateOfPay) {
        var insuranceRepo = new InsuranceMultipliersRepository();
        this.RateOfPay = rateOfPay;
        this._HEALTH_INS = insuranceRepo.GetHealthMultiplier(rateOfPay);
    }
}

关于您的代码,我注意到的另一件事是:您的公共属性具有设置器。这有效地使您的类可变。这意味着您永远无法确定该属性的值没有更改/正确。想象一个场景:创建Pay 对象的实例并设置RateOfPay。您将此对象传递给方法,在不同的地方使用它。然后,您或其他开发人员错误地编写了某种代码:

public void AdjustPayForHours (Pay payToAdjust)
{
    // If someone worked for less than half an hour their rate of pay should be reduced in half
    if (payToAdjust.HoursWorked < 0.5) {
        payToAdjust.RateOfPay = payToAdjust.RateOfPay * 5.0
    }
}

现在,你看看发生了什么。它应该是payToAdjust.RateOfPay * 0.5,但有一个错字会严重影响应用程序/业务的其他部分。这是可能的,因为你的类是可变的。我的个人意见关于定义领域模型的类型、接口和其他对象:没有类property2017 年 3 月 20 日更新添加了遗漏的单词)应该有公共设置器1。所有属性都应该在构造函数中设置。如果需要可变性,则应通过方法更改属性。如果不需要可变性 - 属性应该是只读的,并且 setter 应该不存在或者是私有的2。最好,所有属性都应该是私有的,并且值也应该使用方法检索。同样,这是我个人的观点,它可能是错误的、次优的,但这是我所相信的。这当然是最安全的方式。这是一个示例(基于 C#7,对于 C#5 及更早版本,您需要为每个公共属性提供单独的支持属性):

public class Worker
{
    // Here the property is public, so you'd be able to do the following:
    // var contractor = new Worker("John");
    // string contractorName = contractor.WorkerName;
    public string WorkerName { get; }

    public Worker(string workerName)
    {
         this.WorkerName = workerName;
    }
}

public class Pay
{
    private static double HEALTH_INS { get; }

    // Three properties below are private and are set in the constructor.
    // The "HoursWorked" has a setter because sometimes we need to add hours.
    // Others don't because we set them once when we create an object
    // You're NOT able to do:
    // var someWorkersPay = new Worker();
    // var someWorkersRate = someWorkersPay.RateOfPay;
    private string WorkerName { get; }
    private double HoursWorked { get; set; }
    private double RateOfPay { get; }

    public double GrossPay { get { return HoursWorked * RateOfPay; } }
    private double HealthIns
    {
        get
        {
            return Math.Round((HEALTH_INS * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
        }
    }

    public Pay(Worker workerWithPay, double hoursSpent, double payRate) {
        var insuranceRepo = new InsuranceMultipliersRepository();
        this.WorkerName = workerWithPay.WorkerName;
        this.HoursWorked = hoursSpent;
        this.RateOfPay = payRate;
        this._HEALTH_INS = insuranceRepo.GetHealthMultiplier(rateOfPay);
    }

    public void AddHoursWorked(double hoursToAdd) {
        this.HoursWorked += hoursToAdd;
    }

    public double GetRateOfPay() {
        return this.RateOfPay;
    }

    // Now you can do someWorkersPay.GetRateOfPay();
}

2017 年 3 月 20 日更新:上述实现并未使对象深度不可变,但可以归类为浅不可变。

最后,您询问了更大的应用程序。在更大的代码库中,开发人员使用不同的模式。对于我的场景(数据库中的保险乘数),两个最有用:控制反转和依赖注入。

网上有很多关于这两种模式的文章和文档,我就不赘述了。只是一个简单的解释:使用这些可以让开发人员避免考虑健康保险费率的位置,应该如何获得它(控制反转),并且还允许在我们需要时避免创建存储库的新实例(或其他依赖项)实例化一个新对象。在我的示例中,InsuranceMultipliersRepositoryPay 的依赖项 - 我们需要它来创建新的 Pay。依赖注入允许我们做这样的事情:

public class Pay
{
    private static readonly IHealthInsuranceRepository _insuranceRepo { get; }        

    public string WorkerName { get; set; }
    public double HoursWorked { get; set; }
    public double RateOfPay { get; set; }

    private double HealthMultiplier { get { return _insuranceRepo.GetHealthMultiplier(RateOfPay); } }

    private double HealthIns
    {
        get
        {
            return Math.Round((HealthMultiplier * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
        }
    }

    public Pay(double rateOfPay, IHealthInsuranceRepository healthInsuranceRepository) {
        _insuranceRepo = healthInsuranceRepository;
        this.RateOfPay = rateOfPay;
    }
}

注意:我的代码只是一个示例,建议将关注点与不同的层(存储库、服务、应用程序逻辑、域等)进一步分离。


1:Property Design Guidelines

2:Field Design Guidelines

【讨论】:

  • “不可变的数据结构是未来”并不意味着“每个类现在都应该是不可变的”。差远了。链接我这些“最佳实践”。我想更多的是你富有想象力的重新诠释。
  • @EdPlunkett 没问题:msdn.microsoft.com/en-us/library/ms229006(v=vs.110).aspx 现在,我相信你的下一个答案可能是:这些是指导方针,不是那么具体,这是我的重新解释......好吧,事情是,我不要BS人。当我说一些不是事实的事情时,一定要说这是我的意见。根据我对指南的理解和经验。现在,任何人都可以对指南有不同的理解和不同的体验。无论是前景还是经验都不允许一个人冒犯而不是提出一个案例。
  • @EdPlunkett 哦,还有一个:msdn.microsoft.com/en-us/library/ms229057(v=vs.110).aspx。引用:“封装原则是面向对象设计中最重要的概念之一。该原则表明存储在对象中的数据应该只能由该对象访问。”
  • 我指的是这个奇怪的说法:“C# 中的所有对象都应该是不可变的......我也可以指出 .NET 最佳实践,它们声明相同”向我展示 MS 最佳实践页面,该页面说、暗示或建议“C# 中的所有对象都应该是不可变的”。那个。您使用了“不可变”这个术语,并与一些关于不可变集合的内容相关联,这些内容您要么完全误解了,要么根本没有阅读过。
  • @EdPlunkett 域模型中的所有对象。我已经给了链接。 “解释该原则的一种有用方法是说应该设计一个类型,以便可以在不破坏该类型成员以外的代码的情况下对该类型的字段进行更改(名称或类型更改)。这种解释立即意味着所有字段都必须是私有的。”这是第二个。
猜你喜欢
  • 2016-10-03
  • 1970-01-01
  • 2015-06-09
  • 1970-01-01
  • 2018-07-25
  • 2015-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多