Parameter Action<T1, T2, T3> in which T3 can be optional
Asked Answered
S

3

34

I have the following code:

public static MyMethod()  
{ 
   ...Do something  
   ProtectedMethod(param1, param2);  
   ...Do something  
}  

protected static void ProtectedMethod(IEnumerable<string> param1, string param2, int param3 = 1)  
{  
   ... Do something  
}

Take notice of the optional param3 parameter.

Now for quite a few reasons I need to extract the code of the MyMethod method into its own class but I cannot extract ProtectedMethod with it because of all the classes that are inheriting from this one and I need to keep the changes small and isolated. So I figured I could have an Action<> delegate in the new class with the same signature as ProtectedMethod.

The problem is that if I declare the delegate like this:

protected readonly Action<IEnumerable<string>, string, int> m_ProtectedMethod;

The extracted code does not like it because it says the method is only being invoked with two parameters.

And if I declare the delegate like so:

protected readonly Action<IEnumerable<string>, string> m_ProtectedMethod;

When I send it as a parameter to the new class it does not like it either because the method is defined as having three parameters not two.

So far the only way I have thought of to solve this is to create an overloaded version of ProtectedMethod to eliminate the optional parameter.

Is this the only option or is there another way of doing it since now the preferred choice is to have optional parameters instead of overloaded methods?

Sayles answered 7/10, 2011 at 17:2 Comment(1)
not at all related, but something that looks similar: #708280Violaviolable
D
37

Optional parameters are an attribute of a method or delegate parameter. When you call a signature (method or delegate) that has a known optional parameter at compile-time, the compiler will insert the optional parameter value at the callsite.

The runtime is not aware of optional parameters, so you can't make a delegate that inserts an optional parameter when it's called.

Instead, you need to declare a custom delegate type with an optional parameter:

public delegate void MyDelegate(IEnumerable<string> param1, string param2, int param3 = 1);

When calling this delegate, you will be able to omit the third parameter, regardless of the declaration of the method(s) it contains.

Demolition answered 7/10, 2011 at 17:4 Comment(6)
Can type arguments be optional?Win
@JoelCoehoorn - no - generics create new typesAmy
It seems they are no more powerful than attributes. Could still be useful :)Keffiyeh
@leppie: Optional parameters are attributes. blog.slaks.net/2011/01/optional-parameters-in-c-4.htmlDemolition
@Demolition Thank you so much, this made the solution compile. It's a shame though since with the generic Action you can see right away what is the signature that the method must have, with the delegate you only see MyMethod(MyDelegate myDelegate) on intellisense. Oh well, I guess you sometimes can't get it all.Sayles
@SergioRomero: You need to choose a clear and obvious delegate name. If you want to, you could even make the delegate generic.Demolition
S
1

It would depend on how m_ProtectedMethod would be consumed, but I found a compromise in my own situation, where I use one overload more than the other.

Simply define a simpler (having less generic parameters) Action<> variable, which calls the more complex supplied Action variable method. This can be accomplished either in (i) local scope on use; or (ii) object scope upon assignment of Action property or object construction.

Because there is no such thing as variable/property overloading, you need two different names, for the resulting two related Action variables.

EG i: Local Scope (probably not the most suitable for your scenario)

public MyMethod(Action<IEnumerable<string>, string, int> m_ProtectedMethod2)  
{ 
   Action<IEnumerable<string>, string> m_ProtectedMethod = (p1,p2) => {
      m_ProtectedMethod2(p1,p2,1); //The value 1 is the default 3rd parameter
   }

   ...Do something  
   m_ProtectedMethod(param1, param2);  
   ...Do something  
   ...If something  
      m_ProtectedMethod2(param1, param2, param3); //Calling the more complex form directly
   ...Do something  
}  

EG ii: Object Scope

private Action<IEnumerable<string>, string, int> m_ProtectedMethod2 = null;
private Action<IEnumerable<string>, string> m_ProtectedMethod = null;
protected Action<IEnumerable<string>, string, int> ProtectedMethod
{
   get { return m_ProtectedMethod2; }
   set {
      m_ProtectedMethod2 = value;
      m_ProtectedMethod = (p1,p2) => {
         m_ProtectedMethod2(p1,p2,1); //The value 1 is the default 3rd parameter
      }
   }
}

public MyMethod()
{
   ...Do something  
   m_ProtectedMethod(param1, param2);  
   ...Do something  
   ...If something  
      m_ProtectedMethod2(param1, param2, param3); //Calling the more complex form directly
   ...Do something  
}

Note in both cases I designed the default setting value to be the more awkwardly named variable, having the 2 suffix, such that upon consumption the simpler overload has the more basic variable name.

Sr answered 6/9, 2014 at 3:3 Comment(0)
A
0

Hoping to help others with what I find as being a more elegant implementation of overloading mixed with the (delegate-oriented) strategy pattern.

public class OverloadExample {
    private Action<int, bool> _implementation;

    public OverloadExample() {
        _implementation = defaultImplementation;
    }

    public OverloadExample(Action<int, bool> implementation) {
        _implementation = implementation;
    }

    protected void defaultImplementation(int aInt, bool aBool) {
        //
    }

    public void Implementation(int someInt, bool someBool = true) {
        _implementation(someInt, someBool);
    }
}

Usage:

new OverloadExample().Implementation(9001);
new OverloadExample().Implementation(9001, false);
Agate answered 12/9, 2018 at 20:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.