1

I have a method with a ton of parameters. Some of them are optional. So, in order to use this method easily I use the optional parameter feature.

Moreover, this method builds a Dictionary<string,string> with name of parameter as key of the dictionary and value of the parameter as value of the dictionary for non-null parameters only.

Here is the method :

public string CreateParameterDictionary(
    string id,
    string firstName,
    string lastName,
    string address,
    string postalCode,
    string lorem = null,
    string ipsum = null,
    string dolor = null,
    //...
    string sit = null,
    string amet = null)
{
    if (String.IsNullOrWhiteSpace(id) ||
        String.IsNullOrWhiteSpace(firstName) ||
        String.IsNullOrWhiteSpace(lastName) ||
        String.IsNullOrWhiteSpace(address) ||
        String.IsNullOrWhiteSpace(postalCode))
    {
        throw new ArgumentNullException($"nameof((id) nameof(firstName) nameof(lastName) nameof(address) nameof(postalCode)");
    }

    Dictionary<string,string> parametersDictionary = new Dictionary<string, string>();
    parametersDictionary.Add(nameof(((id),((id);
    parametersDictionary.Add(nameof(firstName),firstName);
    parametersDictionary.Add(nameof(lastName),lastName);
    parametersDictionary.Add(nameof(address),address);
    parametersDictionary.Add(nameof(postalCode),postalCode);

    if (!String.IsNullOrWhiteSpace(lorem)) parametersDictionary.Add(nameof(lorem), lorem);
    if (!String.IsNullOrWhiteSpace(ipsum)) parametersDictionary.Add(nameof(ipsum), ipsum);
    if (!String.IsNullOrWhiteSpace(dolor)) parametersDictionary.Add(nameof(dolor), dolor);
    //...
    if (!String.IsNullOrWhiteSpace(sit)) parametersDictionary.Add(nameof(sit), sit);
    if (!String.IsNullOrWhiteSpace(amet)) parametersDictionary.Add(nameof(amet), amet);

    return parametersDictionary;
}

Can be called with named parameters as:

CreateParameterDictionary(5, "Dexter, "Morgan", "Miami", 12345, dolor: 5);

As you can see, the method is a little bit verbose. I want to know if there is a more concise way to write it (without reflection)

Thank you !

EDIT

Thank you for your answers However, I'm not clear in my question. Just a precision :

  • parameter names in my real method are not param1, param2 etc. but more business name like id, firstName, lastName, address1 = null. Like this when we use this method, it's more easy to know which parameter is mandatory or not. Before this I used params string[] but when I used it i can't have the name of the parameter.

Hope my explanation is more clear now.

Buh Buh
  • 7,443
  • 1
  • 34
  • 61
Florian
  • 4,507
  • 10
  • 53
  • 73
  • You might want to incorporate the example I gave to Radin in one of my comments - `CreateParameterDictionary("a","b","c","d","e",param16:"p");` - where it's clear that the optional parameters will not be "used up" or specified in declaration order, and that's why the `params` solutions are all wrong, if that's your intention. (Of course, in your question, you can flesh out the example more. I needed something to fit in a comment) – Damien_The_Unbeliever Jun 20 '16 at 09:20

5 Answers5

3

Well, a method with that many parameters is for sure a code smell. I would consider to create a support class, to be used as a DTO (Data Transfer Object).

Something simple like:

public class YourBusinessObjectRequestDto
{
    public string id { get; set; }
    public string firstName { get; set; }
    public string lastName { get; set; }
    public string address { get; set; }
    public string postalCode { get; set; }
    ...

    public Dictionary<string, string> ToDictionary()
    {
        var dict = new Dictionary<string, string>()
        {
          { "id", id },
          { "firstName", firstName },
          { "lastName", lastName },
          { "address", address },
          { "postalCode", postalCode },
          { "...", ... }
        };

        return dict.Where(pair => pair.Value != null).ToDictionary(pair => pair.Key, pair => pair.Value);
    }
}

The code is slightly repetitive, but is as simple as it can be, and good enough for my tastes.

If you can trade ease of maintenance with performance, you can leverage the dynamic serialization capabilities of the vast majority of Json libraries.

Using Json.Net, you could do something like:

public Dictionary<string, string> ToDictionary()
{
    var json = JsonConvert.SerializeObject(this);

    var serializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };

    return JsonConvert.DeserializeObject<Dictionary<string, string>>(json, serializerSettings);
}    

It will not be as fast as it can, but as long as you are treating simple types, it will perform just fine and adapt to every parameter you can throw into the list.

This approach has the great advantage of being really simple to debug and manage: no positional parameter is needed.

Edit: I missed the "exclude not null values" requirement. I edited the code to support that.

Alberto Chiesa
  • 7,022
  • 2
  • 26
  • 53
0

Refactor the method to the following and use code contracts to check the params length. In that way you will have compile time errors if you try to use it with less than 5 parameters.

 public string CreateParameterDictionary(
      string param1,
      string param2,
      string param3,
      string param4,
      string param5,
      params string[] optionalParametes)
    {
      Contract.Requires(optionalParametes.Length <= 24);

    }
Radin Gospodinov
  • 2,313
  • 13
  • 14
  • 1
    But now you've lost the name->value mapping since it's not clear whether the first member in the array would have been passed as `param6` or as `param16`. And the names of the parameters are currently used as the keys in a dictionary. – Damien_The_Unbeliever Jun 20 '16 at 08:50
  • @Damien_The_Unbeliever, you haven't lost it. With params you will access the values by their position in the array. So the 6th param is on first places, 7th goes into the second and etc. The same is as optional parameters because you cannot skip the first 10 and set a value for param16 only, you need to fill all of the previous parameters in both cases. – Radin Gospodinov Jun 20 '16 at 08:56
  • 1
    It's perfectly possible to call the OPs existing method as `CreateParameterDictionary("a","b","c","d","e",param16:"p");`. Have you missed the memo on [named and optional parameters](https://msdn.microsoft.com/en-us/library/dd264739.aspx)? – Damien_The_Unbeliever Jun 20 '16 at 09:00
  • @Damien_The_Unbeliever, you are correct, I forgot about this :) – Radin Gospodinov Jun 20 '16 at 09:04
0

In future, if you have that many parameters:

  • Use an array, or
  • Put them in a struct or class instead.

In this case, you can use an array:

public string CreateParameterDictionary(string[] param)
{
    ... same as before, but using param[4] instead of param4, etc...
}
0

Please consider using param keyword instead. Have a look at this example to know the usage of params keyword. It will help you passing variable number of arguments to a method. After the changes your method signature would look something like this:

public string CreateParameterDictionary(
        string param1,
        string param2,
        string param3,
        string param4,
        string param5,
        param string[] variableParams = null,
       )
    {
          //variableParams will contain your parameters from param6 to param30
    }
RBT
  • 24,161
  • 21
  • 159
  • 240
0

I suggest using params instead of list of parameters:

// you want to return Dictionary<String, String>  - right?
// static - I can't see any use of "this" in the method
public static Dictionary<string, string> CreateParameterDictionary(
  params String[] values) {

  if (null == values)
    throw new ArgumentNullException("values");

  // Required: we want at least 5 parameters
  if (values.Length < 5)
    throw new ArgumentException("Too few parameters");

  // First 5 parameters must not be null or empty
  if (values.Take(5).Any(item => String.IsNullOrEmpty(item)))
    throw new ArgumentException("First five parameters must not be null or empty");

  return values
    .Select((item, index) => new {
      index = index + 1,
      value = item
    })
    .Where(item => !String.IsNullOrWhiteSpace(item.value))
    .ToDictionary(item => "param" + item.index.ToString(),
                  item => item.value);
}

Test:

  var result = CreateParameterDictionary("A", "B", "C", "D", "E", "F");

  String report = String.Join(Environment.NewLine, 
    result.Select(pair => String.Format("{0} = {1}", pair.Key, pair.Value)));

  // param1 = A
  // param2 = B
  // param3 = C
  // param4 = D
  // param5 = E
  // param6 = F
  Console.Write(report);
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215