2

I have a page where some products and textfields where the user enters a number. I first use JavaScript to calculate the total cost. Based on how many users they enter they get a different rate (as shown in code below). When the user types or paste a number into a textfield the function CalculateCost gets called which calls the other functions (only showing two in the example, CDCOst and DVDCost) to make sure the fields Monthly Cost and Annual Cost are displaying the correct value.

I of course want to do the final calculation in the code behind as well before I insert into the database. How can I achieve something similiar in C#?

function CDCost() {
                var monthlyAmount;
                var annualAmount;
                var amount;
                var users = $('#txtCD').val();

            if (users > 0 && users < 100) {
                amount = users * 14.95;
                monthlyAmount = amount;
                annualAmount = monthlyAmount * 12;
                return [monthlyAmount, annualAmount];
            }
            if (users >= 100 && users <= 250) {
                amount = users * 12.95;
                monthlyAmount = amount;
                annualAmount = monthlyAmount * 12;
                return [monthlyAmount, annualAmount];
            }
            if (users == 0) {
                monthlyAmount = 0;
                annualAmount = 0;
                return [monthlyAmount, annualAmount];
            }
        }

function DVDCost() {
                var monthlyAmount;
                var annualAmount;
                var amount;
                var users = $('#txtDVD').val();

            if (users > 0 && users < 100) {
                amount = users * 16.95;
                monthlyAmount = amount;
                annualAmount = monthlyAmount * 12;
                return [monthlyAmount, annualAmount];
            }
            if (users >= 100 && users <= 250) {
                amount = users * 14.95;
                monthlyAmount = amount;
                annualAmount = monthlyAmount * 12;
                return [monthlyAmount, annualAmount];
            }
            if (users == 0) {
                monthlyAmount = 0;
                annualAmount = 0;
                return [monthlyAmount, annualAmount];
            }
        }


        function CalculateCost() {
            var cd = CDCost();
            var dvd = DVDCost();

            var monthlyCost = cd[0] + dvd[0];
            var annualCost = cd[1] + dvd[1];

            return [monthlyCost, annualCost];
        }

        $('#txtCD').bind('keyup change', function (ev) {
            var cost = CalculateCost();
            var monthly = cost[0];
            var annual = cost[1];

            $('#MonthlyCostSum').text(monthly);
            $('#AnnualCostSum').text(annual)
        });

How would I go on doing this in C#?

Something like:

protected double CDCost()
    {
        double monthlyAmount;
        double annualAmount;
        double amount;
        double users = Convert.ToDouble(txtCD.Text);

        if (users > 0 && users < 100)
    {
        amount = users * 14.95;
        monthlyAmount = amount;
        annualAmount = monthlyAmount * 12;
        return //how do I return the values here to a CalculateCost function?
    }


}
martin
  • 35
  • 1
  • 1
  • 5

9 Answers9

8

Either use out parameters or create a new type wrapping all the things you would like to return (a so-called "property bag"):

class ReportData
{
    public double MonthlyAmount { get; set; }
    public double AnnualAmount { get; set; }
    public double Amount { get; set; }
}

...

protected ReportData CDCost()
{
    return new ReportData() 
        { 
            Amount = users * 14.95
            MonthlyAmount = amount, 
            AnnualAmount = amount * 12.0,
        };
}
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • Though it's an ugly hack, sometimes you can also piggyback on existing framework datatypes. For example, if you need to return just 2 values, you could use `System.Collections.Generic.KeyValuePair`. – Vilx- Oct 02 '10 at 13:14
  • typo - function declaration returns double.. should be ReportData – Gishu Oct 02 '10 at 13:16
  • 2
    Oh, not to mention that C# 4.0 has a `Tuple` type just for this case. – Vilx- Oct 02 '10 at 13:19
  • @Vilx: I definitely would not recommend doing this. Surely it "works" but it doesn't express the intent of the code very well (`KeyValuePair` implies a key-value relationship). Writing your own type is not costly at all (3 lines of code effectively), gives type safety, and can clearly express your intent. – Dirk Vollmar Oct 02 '10 at 13:24
  • @0xA3 - I agree, that's why I called it "an ugly hack". But sometimes what you return does in fact have a Key-Value relationship (or close to that), so then it makes sense to use it. – Vilx- Oct 02 '10 at 13:44
  • @Vlix: Tuples have the drawback, that the members have no self-describing name. That being said, I also think you could use them here, because the return values are so closely related (daily, monthly and annual). But if that is not the case, I would prefer a struct or out-parameter. – ollb Oct 02 '10 at 13:54
  • thanks for the comments guys. It solved my problem, however, I realized I could might as well return the monthlyCost only and then multiply it by 12 both in the code behind and in the javascript. – martin Oct 06 '10 at 00:30
2

The tuple class in .NET 4 does the job perfectly.

protected Tuple<double, double, double, double> CDCost()
    {
        double monthlyAmount;
        double annualAmount;
        double amount;
        double users = Convert.ToDouble(txtCD.Text);
   if (users > 0 && users < 100)
    {
        amount = users * 14.95;
        monthlyAmount = amount;
        annualAmount = monthlyAmount * 12;
        return //how do I return the values here to a CalculateCost function?
    }
    return new Tuple<double, double, double, double>(monthlyAmount, annualAmount, amount, users);
}
JP Hellemons
  • 5,977
  • 11
  • 63
  • 128
Echilon
  • 10,064
  • 33
  • 131
  • 217
0

I know this thread is old but just thought I'd add my 2 cents: What about some read-only class properties in the same class that contains the function (which could be turned into a Sub at that point)? This is to me the cleanest solution that makes sense....

in VB:

Private iAmount  As Integer
Public ReadOnly Property Amount() As Integer
    Get
        Return iAmount
    End Get
End Property

Private iMonthlyAmount  As Integer
Public ReadOnly Property MonthlyAmount() As Integer
    Get
        Return iMonthlyAmount
    End Get
End Property

Private iAnnualAmount  As Integer
Public ReadOnly Property AnnualAmount() As Integer
    Get
        Return iAnnualAmount
    End Get
End Property
0

You could either use out parameters to return multiple values

OR

You could return a collection (e.g. a double array) or custom object which contains all the objects that you want to return.

class MyCustomType
{
  double AnnualAmount{get; set;}
  double MonthlyAmount{get; set;}
}

// and in your function you could go
return new MyCustomType{ AnnualAmount = 4.5d, MonthlyAmount = 5.5d  };
Gishu
  • 134,492
  • 47
  • 225
  • 308
0

You can use an array, like this:

protected double[] CDCost() {
    //a lot of code
    return new double[2] { annualAmount, monthlyAmount };
}

and then use it like this:

double cdCost = CDCost();
annualAmount = cdCost[0];
monthlyAmount = cdCost[1];
Cokegod
  • 8,256
  • 10
  • 29
  • 47
0

Would have changed the way you do it now, to return a CDCost object:

public Class CDCost
{
  public double Monthly {get;set;}
  public double Annually {get return Monthly*12;}  

  public CDCost(double monthly)
  {
    Monthly=monthly;
  }
}

But if you're interested in c# syntax - you can use out or an array for example (Although I think both of them are bad design in this case).

Oren A
  • 5,870
  • 6
  • 43
  • 64
0

In a technical, somewhat smartass sense, the answer is that you can't return more than one value from a function. A function either returns one value or doesn't return anything. This goes for your Javascript example--it's not actually returning two values, it's returning one value, which is an array of two elements.

That said, here are your options, as already pointed out by others:

1) Return an array. This is what you did in the Javascript example, and it would work anywhere else, too. The problems I see are two: one, you're limited to one type (and, if applicable, its subtypes). If you want to return, say, a double and a string, you'd need to make it return an array of Objects and cast to the appropriate type. Two, it makes for a fairly difficult-to-use API, because ordering is crucial. Any code consuming your function would need to know that returnValue[0] represented, say, the monthly amount and returnValue[1] represented the annual amount. Sure, you could say that in the documentation, but it's still a tricky API to use. And God help you if you (or someone else) changes the order of the return values for some reason (possibly through carelessness). That would be a horrible bug to track down.

2) Return a dictionary. This avoids the ordering problem, but it makes up for that by adding the key problem--anyone using your code would need to know that the dictionary returned used some specific set of keys (say, "monthlyAmount" and "annualAmount"). A typo, either in consuming code or in the method's body, would be a pain in the ass. And just like the array solution, you'd have to be sure to document the key values. It also suffers from the same type issue that the array solution poses. It would probably also be a bit less performant than the array solution, although it's questionable whether it would really make a noticable difference.

3) out parameters. This avoids the previous problems in that they can each be of any type and the compiler/IDE will catch any typos/bad assignments. However, in general I try to avoid out parameters (as does FxCop). Personally, they seem to be abusing the parameter syntax--parameters are for passing data into the function, and out parameters, while they do technically do that, are being used for return values rather than values to be used by the function. It also pollutes the method signature--it's n more parameters you need to pass, which is particularly annoying if you want a bunch of overloads (and you're not using .NET 4.0's optional/named parameters). On a much more minor note, it's a bit annoying to have to declare return values before they can be returned, i.e.

double outparam1, outparam2;
SomeFunction(out outparam1, out outparam2);
//use out parameters

4) Return an instance of a custom class. It's a bit ugly to add an entire class specifically for the return value of a single method, but this is probably the most elegant solution, in that it tells people using the method exactly what's returned and you can mix types (and the method doesn't have to take extra parameters just to be able to return things, as with out parameters, which makes the method calls a little cleaner). However, I wouldn't go overboard with this--if you find yourself with lots of methods that need to return multiple (very different) values (you shouldn't find yourself this way), then adding a new return class for each method would, I think, make your life much more hellish.

5) Not return multiple values. This may not always be an option, but if you find yourself wanting to return multiple values, I'd look hard at whether the values need to be returned together, or if you can split it into two calls (GetMonthlyAmount() and GetAnnualAmount(), for instance). Sometimes this wouldn't make sense, particularly if your method is does expensive things like make a database call.

tl;dr: when you want to return multiple values, first make sure that you can't split it into separate methods, then for a custom return class. Within reason.

Turner Hayes
  • 1,844
  • 4
  • 24
  • 38
0

I would say named touple is the new way to go (since C#7, apparently)

protected (double amount, double monthlyAmount, double annualAmount) CDCost()
    {
        double monthlyAmount;
        double annualAmount;
        double amount;
        double users = Convert.ToDouble(txtCD.Text);

        if (users > 0 && users < 100)
        {
            amount = users * 14.95;
            monthlyAmount = amount;
            annualAmount = monthlyAmount * 12;
            return (amount, monthlyAmount, annualAmount);
        }
        throw new NotImplementedException();
    }

and then you can call it implicitly using the deconstructor

    var (amount, monthlyAmount, annualAmount) = CDCost();
    
Mosè Bottacini
  • 4,016
  • 2
  • 24
  • 28
-1

The best way to do this is to return a dictionary with the values

protected Dictionary<string, double> CDCost()
    {
        Dictionary<string,double> values = new Dictionary<string,double>();
        double monthlyAmount;
        double annualAmount;
        double amount;
        double users = Convert.ToDouble(txtCD.Text);

        if (users > 0 && users < 100)
    {
        amount = users * 14.95;
        values["monthlyAmount"] = amount;
        values["annualAmount"] = monthlyAmount * 12;
        return values
    }

}

AutomatedTester
  • 22,188
  • 7
  • 49
  • 62
  • This is too loosely bound - how is another programmer going to know what the name of the 'properties' are on the return value? Or what would you do if you wanted to return a `double` and an `int`? Definitely not 'the best way'. – Kirk Broadhurst Oct 02 '10 at 13:32
  • I think that this is rather expensive (memory overhead, string comparison, hashing), limited to one type (unless you resort to boxing) and prone to typing errors (ie. you don't get a compile error if you type annulAmount). Out-parameters are there for exactly that situation. They are fast, guaranteed to be set, typed, the compiler will detect typos and may apply more optimizations. Also the return value names aren't hidden in the implementation. – ollb Oct 02 '10 at 13:39
  • granted it will be extremely loosely bound but it does also mean that if they decide they need to get more information, e.g. users, they don't have to change the signature of the method. Both ways have their merits. – AutomatedTester Oct 02 '10 at 16:22