0

Say I have a Calculator class similar to this:

using System;

public class CalCulator
{
    public int Square(int num)
    {
        return num*num;
    }

    public int Add(int num1, int num2 )
    {
        return num1 + num2;
    }

    public double Add(double num1, double num2 )
    {
        return num1 + num2;
    }

    public int Multiply(int num1, int num2 )
    {
        return num1 * num2;
    }

    public int Subtract(int num1, int num2 )
    {
        if ( num1 > num2 )
        {
            return num1 - num2;
        }

        return num2 - num1;
    }
}

This shows how to use Calculator class from Main C# program.

public class Class1
{
    static void Main()
    {
        CalCulator sq = new CalCulator();
        Console.WriteLine( sq.Square(8).ToString());
        Console.WriteLine( sq.Add(8.3, 9.24).ToString());
        Console.WriteLine( sq.Multiply(5,8).ToString());
        Console.WriteLine( sq.Subtract(22, 42).ToString());
    }
}

Source (including lack of formatting): A Simple Calculator Class by Mahesh Chand

Say all of the methods accept two arguments (I realise this is not true in the example I have provided as it contains a method called: Square, which accepts one argument).

Would it be "better" to use a data object that groups the two arguments together? As I believe this would help with testability (inversion of control, dependency injection, et cetera)?

Is there a minimum number of arguments required before one should use a data object to pass them to a method?

Update

The Calculator is perhaps not the best analogy to explain my question. Say I have a function that returns a list as follows:

public List<PercentageValues> CalculateTaxGroupedByPercentage()
{
}

public class PercentageValue()
{
    private int decimal _percentage;
    private readonly decimal _value;

    public PercentageValue(int percentage, decimal value)
    {
       _percentage = percentage;
       _value = value;
    }
}

For example, if a person has a salary of £60,000, then CalculateTaxGroupedByPercentage() will return the following list:

List<PercentageValue> list = new List<PercentageValue>();
list.Add(new PercentageValue(20,45000);
list.add(new PercentageValue(40,15000);

Two questions:

1) Is this a valid DTO/Value type? 2) What is the difference between a DTO and value type? I thought a DTO was for transferring data over application domains e.g. a web service returning a class to a client. In that case this is not a DTO.

  • You're using two terms incongruously. Value types don't have parameters. – Robert Harvey Jun 21 '17 at 21:55
  • @Robert Harvey, thanks. I have edited the question. Does it make sense now? – w0051977 Jun 21 '17 at 21:56
  • 3
    You're just saying the same thing. Why does Eric Evans have any special authority here? Read the article I linked; it explains that many Value Types have parameterless constructors (zero parameters required). – Robert Harvey Jun 21 '17 at 22:02
  • 3
    I don't have Eric's book in front of me, so I can't comment on that. On its face, it sounds ridiculous. – Robert Harvey Jun 21 '17 at 22:23
  • 1
    AS an aside, consider some reasonable indentation. It enourmously helps clarity. – Deduplicator Jun 21 '17 at 22:30
2

The first comment to make here is that, when using any .NET language you need to be careful over the use of the term value type. C#'s structs are value types as they are passed by value, not by reference. The term I've commonly seen used for what you are asking about is "value objects", but even that can be confusing as they maybe structs.

Regarding whether the following code is a valid "DTO":

public class PercentageValue()
{
    private int decimal _percentage;
    private readonly decimal _value;

    public PercentageValue(int percentage, decimal value)
    {
        _percentage = percentage;
        _value = value;
    }
}

The answer is "no" simply because that code doesn't allow the values to be read from it, so it's pretty useless. Fix it and then yes, it is valid simply because it's a means of transferring data between methods:

public class PercentageValue()
{
    public int Percentage { get; }
    public decimal Value { get; }

    public PercentageValue(int percentage, decimal value)
    {
        Percentage = percentage;
        Value = value;
    }
}

Regarding "What is the difference between a DTO and value type?", the answer is: semantics. Strictly speaking, in C#, value types are structs and enums and are unrelated to DTO's. But in the way you are using them in your question, they are the same thing.

In your calculator example. As others have said, it makes complete sense to have two parameters, rather than bundling the values into a transient object to pass in as one parameter. Further, as they are all pure functions, make them static, rather than needlessly creating an object just to reference them.

One last thing, unless there's a really good reason why you can't do so, use VS2017 and C# 7 to avoid having to create your "DTO" and just return a tuple:

IEnumerable<(decimal percentage, decimal value)> CalculateTaxGroupedByPercentage()
{
    yield return (20,45000);
    yield return (40,15000);
}
  • Thanks. This answer was helpful +1. Why would you use a Tuple instead of a type. Surely this would make it more difficult for another developer to understand what you are doing? What regards to the 'static' point; this would make testing much more difficult. – w0051977 Jun 22 '17 at 9:00
  • @w0051977, please see this answer for details of tuples versus classes. I really don't see how a simple tuple is harder to understand than a DTO that does the same thing, just in a far more verbose fashion. – David Arno Jun 22 '17 at 9:13
  • 1
    @w0051977, Your methods are pure functions and so are totally deterministic in behaviour, therefore they should be tested directly and never mocked. So making them static makes them easier to test. – David Arno Jun 22 '17 at 9:14
  • Tuple v Class is similar to Class v KeyValuePair, which is debated here: softwareengineering.stackexchange.com/questions/107889/…. The general consensus seems to be to use a class. – w0051977 Jun 22 '17 at 9:19
  • 1
    @RobertHarvey, the old-style tuples suffered from that. C# 7's new tuples (System.ValueTuple<...> and associated syntactic sugar) allow the fields to be named, such as percentage and value in the example code in this answer. – David Arno Jun 22 '17 at 15:00
8

I think you are asking should you change this

double Add(double lhs, double rhs) {
    return lhs + rhs;
}

to this:

class AddParameters {
    public double LHS { get; set; }
    public double RHS { get; set; }
}

double Add(AddParameters params) {
    return params.LHS + params.RHS
}

The answer is no, that would be utterly ridiculous. Well maybe it'd be okay in a functional programming language, but not c#.

The place where this principle would apply would be with a series of related parameters that are cumbersome to pass around, e.g. instead of

void AddUser(string firstName, string lastName, string street, string city, string state)

you'd perhaps use something like

class UserDto
{
    string FirstName { get; set; }
    string LastName { get; set; }
    string Street { get; set; }
    string City { get; set; }
    string State { get; set; }
}

void AddUser(UserDto user)

Is there a magic number of parameters? No, it's a judgment call. You'd switch to using some sort of DTO or parameter object if it becomes too painful to pass the data separately, or if you need to persist or transmit the parameters as a group (e.g. passing the user to a separate function to print), or if you have several functions that all need the same group of parameters.

  • I have added an update to the question. Could you take a look? – w0051977 Jun 22 '17 at 7:13
  • I agree that it is a little ludicrous, though it could be acceptable if additional logic were required on the parameters, which rather than handle it in the called method, it could be handled in a new class responsible for providing the parameters in question. – Neil Jun 22 '17 at 8:23
7

Is there a minimum number of arguments required for a value type?

I think there is a better way to look at this than by the count of parameters to a method as a complexity threshold, and it goes to abstraction: to the implicit grouping of pairs (or triples or more) that clients manage in their code vs. explicit grouping that manifest as a single programmatic entity for clients to manage.

When a method has many parameters often several of the parameters are closely related: so closely related in fact that they are treated as a group by clients, in other words, we have an implicit concept. So, the idea is not necessarily to create a data object that holds all the parameters for the method, but rather identify the one or more implicit concepts in the parameter list and regroup those together into explicit abstractions.

For example, foo ( int x, int y, int count ) -- here we might want to take the x,y coordinate and group that together, moving it from an implicit concept to an explicit abstraction. This without necessarily affecting the count parameter, which remains as is (which is to say we're not necessarily creating an abstraction for the whole parameter list in its entirety).

A "point" having int x and int y is a better abstraction than two int's for several reasons. First and foremost, the point, once created can be treated by the client as a single entity to use and to manage and reason over. Second, in most languages, it will have additional type safety. Third, when calling a method that takes multiple points, it will be much harder to have a typo like passing x1, y1, x1, y2 when the latter was intended to be x2, y2. Further, the point entity may provide basic methods (e.g. serialization & deserialization) that make the abstraction stronger.


Common among calculation engines and expression evaluation is a method of storing multiple operands, for example using an operand stack. Using that as the abstraction, you can reduce the number of parameters, maybe even to zero if you share the stack in the constructor. The stack abstraction provides a single entity that groups multiple operands together. We can even eliminate the return values as well, pushing the result back onto the stack.

Of course, in this simple example, the different types of the calculator might be handled by having two different stacks (an int stack, a double stack) or by type tagging and type conversions (e.g. promotion of int to double).

  • I have added an update to the question. Could you take a look? – w0051977 Jun 22 '17 at 7:14
  • A DTO is simple object that has no logic so that it can cross domain boundaries, including going across a wire. A Value Object is an full fledged Entity/abstraction in your domain, and can have domain/business and persistence logic associated with it. See here. The terms are frequently confused or conflated. – Erik Eidt Jun 22 '17 at 14:59
1

Is there a minimum number of arguments required for the use of a data object?

Really, It's not about the number of arguments. It's about how they make the using code look.

I've been watching this question for a while and I see some good answers here. Normally I'd just leave it alone but I can't shake the feeling that an important point is being missed here.

Console.WriteLine( sq.Add(8.3, 9.24).ToString());

An issue with that might not have anything to do with that there are only 2 arguments being passed as opposed to say 15. It might be that whatever uses this add method has to KNOW that there are two at the same time it has to know it wants to add.

There is a well documented refactoring that you should look up here. It's called Introduce Parameter Object. The main thing it does is let you make the previous line look like this:

Console.WriteLine( sq.Add(operands).ToString());

The reason to do this isn't because it's so much easier to deal with 1 vs 2 arguments. It's that now, right here, we don't know if the number of operands are 1, 2, or 42. That means this interface is less coupled to that implementation detail and can vary more independently. The reduction in coupling is magnified if there are many methods that use the same operands. We don't even know if operands is a DTO, a tuple, a collection, or a even a behavior object that going to take some add lambda. We don't want to know.

Some people actually find that lack of knowledge annoying. To them this might even be considered harder to read. So don't do this thinking fewer is always better for reading. No, this is about being sure we're flexible and ready to accept changes.

When it is about readability a data object is hardly the only option. Using named arguments can help reduce the pain of arity since you're not asking people to remember the order of arguments. In a language without name arguments you can use builder patterns like the Josh Bloch Builder that effectively simulates them.

Sometimes arguments are packed together that are not first discovered all at the same time. These can be separated by when they are learned. This is one of the big things I think about when deciding what arguments can go in the constructor.

Sometimes it's not readability but you're being forced to deal with arguments you'd rather not deal with since they have nothing to do with your responsibility. In those cases what you really need is a reference to a behavior object that already knows what it needs to know that you can just tell to do things.

And yes sometimes a data/parameter object will improve readability. Really this is all about treating the argument list as something more then just a place to pile up dependencies. It's nice if the pile can be organized. A data object is simply one of many ways to organize it.

The best organization comes with good abstractions. It would be justifiable to question how readable Add(operands) is but Display(point) seems very natural. Structurally, they are no different. But the abstraction is much tighter because it's very easy to imagine exactly what is hiding under the name point.

It's when you fail to respect this need not to surprise people that they become obsessed with flattening everything out. Looking at a ton of arguments is a pain but it's better then having to guess what LHS stands for.

In short:

  1. Nicely organized with good names and consistent abstractions, which are better than
  2. Flat straightforward lists of everything, which are better than
  3. Disorganized structures, mysterious names, and leaky abstractions.

Many people prefer 2 precisely because it's hard to tell whether you are creating 1 or 3. That's why we have peer reviews. Don't attempt 1 without a buddy.

0

I have never heard of any rule, best practice, or guideline that says if you have more than X number of parameters you should wrap them with a class and pass the class object as a single parameter. I'd say it is up to your personal coding style and your gut.

That being said, if you are (or will be) passing the same set of parameters around to other methods/classes and rarely (if ever) use the individuals attributes on their own, then that should raise a flag that it would be worth seriously thinking about putting into class. That would lead to cleaner code and better maintainable code.

As an example, lets consider a "person" which has a name. That's just two string values, so basing off some rule that 5 or less should not be a class and should be parameter passed around, you have an app that has a lot of methods that deal with the person and those two attributes. Now someone reminds that there should be a middle name. Now you have to visit all those places to add the third parameter.

Now someone else reminds that some people go by their first name, some go by their middle name, and some go by a nickname. So now you are adding more parameters in all those places, plus some value that tracks if the person wants to be called by their first, middle, or nickname.... and now throughout your code you are now finding all the places where the person is presented a welcome greeting, and inserting that logic.

Hopefully before you get too far down that path you'll back up and say "person" should be a class. The quicker you decide to do that, the less headache you experience.

Also, as your example shows, when you want to use classes like the language built-in Collections (List, etc.) to handle your "group" of attributes, that is a sure sign you need to wrap those attributes with a class. And it won't be too far down the road that you need to persist your attributes to storage, serialize/deserialize for integrating with other microservices, etc.

-5

Value is a single value. You could call a function and say its result is a single value like:

function example($a, $b){
  return $a + $b; 
}
Value X = example(a, b); 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.