简体   繁体   中英

C# how do I pass optional parameters as a collection?

Assuming I have 3rd party MethodA(type1 paramName1 = null, type2 paramName2 = null,...., nonNullAbleSuchAsInt paramName10 = 123) .

And I want do the following by calling the same MethodA using the provided data given by the user. User may give me data for paramName2 and paramName4, or some other permutations such as [paramName2, paramName3, paramName5, paramName7] and etc.

if(user gives me data for paramName2 and nothing else)
{
  MethodA(paramName2=userData2)
}
else if(user give me data for paramName2, paramName3, and nothing else)
{
  MethodA(paramName2=userData2, paramName3=userData3)
}
else if(user give me data for paramName2, paramName4, and nothing else)
{
  MethodA(paramName2=userData2, paramName4=userData4)
}
else if(user give me data for paramName2, paramName3, paramName4, and nothing else)
{
  MethodA(paramName2=userData2, paramName3=userData3, paramName4=userData4)
}
... repeat for all permutations.

But, that's so many duplicated code.

I want to do the following. How do I do it?

MagicStorage<MethodA_Declaration> magicArgs = new MagicStorage<MethodA_Declaration>();

if(user gives me data for paramName1)
{
  magicArgs.Add(paramName1, userData1);
}

if(user gives me data for paramName2)
{
  magicArgs.Add(paramName2, userData2);
}

... repeat

if(user gives me data for paramName10)
{
  magicArgs.Add(paramName10, userData10);
}

MethodA(magicArgs);

And if I made a mistake like userData10 is not the same type needed by paramName10 , I get editor and compiler error.

Is this possible? I don't want to make a method call for all the permutations of user input data. There would be too much code to manage.

Given

MethodA(type1 paramName1 = null, type2 paramName2 = null, int paramName10 = 132).

Usage

public class data
{
    public type1 ParamName1 {get;set;}
    public type2 paramName2 {get;set;}
    // initalize with what ever default the method takes as optional
    public int paramName10 {get;set;} = 123; 
}

...

// pass in all paramaters, dont both about ifs
// we can do this, because you have already figured out the defaults
// from the signature 
MethodA(data.ParamName1, data.paramName2, data.paramName10);

One way would be to use reflection to call the method with an ordered array of parameters, where the user specified values are used where possible and Type.Missing used where no value was specified:

public static object InvokeWithOrderedParameters(object instance, string methodName,
    IDictionary<string, object> namedParameters)
{
    // Get the method to invoke
    var method = instance.GetType().GetMethod(methodName);

    // Get an array of ordered parameter values based on the specified named 
    // parameters, with a default value of "Type.Missing" for any missing names 
    var orderedParams = method.GetParameters().Select(param =>
    {
        object value;

        // Set the value from our dictionary, or if that fails use "Type.Missing"
        if (!namedParameters.TryGetValue(param.Name, out value))
        {
            value = Type.Missing;
        }

        return value;
    }).ToArray();

    // Invoke the method with the ordered parameters and return the value
    return method.Invoke(instance, orderedParams);
}

With this method, we can pass the instance of our type, the name of the method to invoke, and a Dictionary<string, object> of named parameters and their values, and it will return the result of calling that method with the parameters that we specified.

As an example, here's a method that has default values for all it's parameters:

public class ThirdParty
{
    public string MethodA(string arg1 = "defaultArg1", string arg2 = "defaultArg2",
        string arg3 = "defaultArg3")
    {
        return $"{arg1}, {arg2}, {arg3}";
    }
}

And we can use our reflection method to call it with as many named parameters as we like. Below I'm just giving a value for the second parameter:

public static void Main(string[] args)
{
    var namedParameters = new Dictionary<string, object>
    {
        {"arg2", "custom Arg 2 value"}
    };

    var instance = new ThirdParty();
    var result = InvokeWithOrderedParameters(instance, "MethodA", namedParameters);

    Console.WriteLine(result.ToString());

    GetKeyFromUser("\nDone! Press any key to exit...");
}

Output

As you can see, the value we specified was passed, and the default values were used where we didn't specify anything:

在此处输入图像描述

You can make it somewhat more readable using the switch support for pattern matching .

And of course you can replace any switch with a Dictionary and a loop, as long as the value has place for a function delegate. Of course in this case, the Key might need to have a delegate too.

Those would make the code more readable, but can not avoid the write work.

Beyond that, there is only moving the resolution of the function calls out of compile time and into runtime. A step I am not a friend off and thus have little experience in.

You can have something like

int param1 = default(int);
double param2 = default(double);
MyClass param3 = default(MyClass);
 ....
int param10 = 123;
ReadUserData(out param1, out param2, out param3, ..., out param10)

Then call MethodA :

MethodA(param1, param2, param3, ..., param10)

NOTE: Starting with C# 7.0 (I think) when using out parameters there is a sugar that let you declare the variables with the method call (so you don't have to declare them one by one), for example, the call to ReadUserData can be done as:

ReadUserData(out int param1, out double param2, out MyClass param3, ..., out int param10)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM