views:

571

answers:

5

I have two methods in C# 3.5 that are identical bar one function call, in the snippet below, see clientController.GetClientUsername vs clientController.GetClientGraphicalUsername

    private static bool TryGetLogonUserIdByUsername(IGetClientUsername clientController, string sClientId, out int? logonUserId)
    {
        string username;
        if (clientController.GetClientUsername(sClientId, out username))
        {
     // ... snip common code ...
        }

        return false;
    }

    private static bool TryGetLogonUserIdByGraphicalUsername(IGetClientUsername clientController, string sClientId, out int? logonUserId)
    {
        string username;
        if (clientController.GetClientGraphicalUsername(sClientId, out username))
        {
     // ... snip common code ...
        }

        return false;
    }

Is there a way (delegates, lamda's ?) that I can pass in which method on clientController I want to call?

Thanks!

A: 

You can pass a MethodInfo, which can be looked up statically. However, I agree that a redesign may be called for.

private static readonly MethodInfo getRegularLogin = typeof(IGetClientUsername).GetMethod("GetClientUsername");
private static bool TryGetLogonUserIdByUsername(IGetClientUsername clientController, string sClientId, out int? logonUserId)
{

    string username;
    return TryGetLoginReflective(getRegularLogin, clientController, sClientId, out username, out logonUserId);
}

private static readonly MethodInfo getGraphicalLogin = typeof(IGetClientUsername).GetMethod("GetClientGraphicalUsername");
private static bool TryGetLogonUserIdByGraphicalUsername(IGetClientUsername clientController, string sClientId, out int? logonUserId)
{
    string username;
    return TryGetLoginReflective(getGraphicalLogin,  clientController, sClientId, out username, out logonUserId);
}

private static bool TryGetLoginReflective(MethodInfo method, IGetClientUsername clientController, string sClientId, out string username, out int? logonUserId)
{
    object[] args = new object[]{sClientId, null};
    if((bool)method.Invoke(clientController, args))
    {
         // ... snip common code ...
    }
    logonUserId = ...;

    username = (string)args[1];
    return false;
}
Matthew Flaschen
A delegate is a much better way to go about this than reflection.
Jonathan
+9  A: 

While you can pass a delegate as a parameter, I suggest going with a different route. Encapsulate the body of the if statement which involves common code in another function and call that one in both functions.

Visual Studio has a "Refactor -> Extract Method" feature in the context menu. You can just fill in one of the bodies, select the body and use that feature to extract a method out of it automatically.

Mehrdad Afshari
I agree, a redesign would be better.
Enyra
I also agree: Redesign! That's a typical case where you start complicating things that are simple. KISS.
Nelson Reis
This is perfect advice for programming in an imperative style (especially if there are only two possible functions), but actually passing the function allows for a pretty decent emulation of the functional-language feature of partial application.
Mark Rushakoff
@Mark: I agree that this style makes sense in functional languages but C# is not a pure functional language. While LINQ and lambdas are really great and add a functional sense to the language, it *doesn't replace the imperative aspects* of C#. I don't see any reason to sacrifice simplicity of the language when the imperative style is more suited.
Mehrdad Afshari
+1  A: 

The type of a function is written as Func < inParam1, inParam2, ..., returnParam>. I'm not sure offhand if "out" parameters are passed properly in "Func" types, but you should be able to make your function like

void TryGetLogon(Func<IGetClientUsername, string, out int?, bool> f) { 
   // ...
   f(x, y, z, a);
}
// ...
TryGetLogon(TryGetLogonUserIdByGraphicalUsername);
Mark Rushakoff
"out" and "ref" are not legal in type arguments. You'll have to define a new delegate type.
Eric Lippert
A: 

How about simply passing in a boolean flag?

private static bool TryGetLogonUserIdByUsername(
    IGetClientUsername clientController,
    string sClientId, out int? logonUserId, bool graphical)
{
    string username;
    bool gotClient = false;

    if (graphical)
    {
        gotClient = clientController.GetClientGraphicalUsername(
            sClientId, out username);
    }
    else
    {
        gotClient = clientController.GetClientUsername(
            sClientId, out username);
    }

    if (gotClient)
    {
               // ... snip common code ...
    }

    return false;
}
Bing
Good idea; although I prefer the delegate approach - less code to communicate the same intent
David Laing
When given the option, I prefer to avoid boolean flags like this. They move functional description of the function you're calling from the function name to the parameter list. (In this case, the end of the parameter list). Also, the meaning of the boolean isn't apparent at the callsite-- you have to look up the parameter name to figure out what it means.
Greg D
+6  A: 

Sure. Just define a delegate like so:

public delegate bool GetUsername(string clientID, out string username);

And then pass it into your function and call it:

private static bool TryGetLogonUserId(IGetClientUsername clientController, string sClientId, out int? logonUserId, GetUsername func)
{
    string username;
    if (func.Invoke(sClientId, out username))
    {
       // ... snip common code ...
    }
    return false;
}

To call the function with the delegate, you'll do this:

TryGetLogonUserId(/* first params... */, clientController.GetClientUsername);
Josh G
func.Invoke... poor style...
leppie
What are you saying is poor style? Passing in the delegate? I was just answering the question. Calling ".Invoke()" on the delegate? I prefer that notation because it makes it clear that you are using a delegate.
Josh G
I'm with Josh G, this seems quite an elegant solution to the problem, and if you name "func" carefully its clear what is happening
David Laing
I agree with Josh G. There's absolutely nothing "poor" in calling Invoke on a delegate instance.
Daniel Daranas