views:

111

answers:

4

I'm currently rewriting parts of a custom RPC mechanism (which cannot be replaced by something else, so don't suggest that ;-) ). The arguments of a call are collected in a custom collection that uses a dictionary internally. There is a method T Get<T>(string) to retrieve a named argument. For optional arguments, I wanted to add a TryGet<T>(string) method that returns the argument or null if it doesn't exist, so that the calling code can provide a default value using the null coalescing operator. Of course, for a value type this doesn't work, but I could use T? instead, which is what I want.

So what I have is this:

public class Arguments
{
    // lots of other code here

    public T TryGet<T>(string argumentName) where T : class
    {
        // look up and return value or null if not found
    }

    public T? TryGet<T>(string argumentName) where T : struct
    {
        // look up and return value or null if not found
    }
}

With that, I'd like to be able to do the following:

return new SomeObject(
                      args.TryGet<string>("Name") ?? "NoName",
                      args.TryGet<int>("Index") ?? 1
                      );

Since the constraints are mutually exclusive, the compiler should be able to produce the correct code (it's always possible to infer the call from the generic type given at the call site). The compiler complains that the type already defines a member called "TryGet" with the same parameter types.

Is there any way to make something like this work without giving the two methods different names?

+1  A: 

The reason this doesn't work is because you cannot have two methods with the same name and same argument types (the return type is not taken into account for method overloading). Instead you could define a single method without the generic constraint which will work for both value and reference types:

public T TryGet<T>(string argumentName)
{
    if (!_internalDictionary.ContainsKey(argumentName))
    {
        return default(T);
    }
    return (T)_internalDictionary[argumentName];
}
Darin Dimitrov
That won't give the desired `Nullable<T>` behaviour for value types though.
Jon Skeet
The desired behavior could be achieved with: `args.TryGet<int?>("Index") ?? 1`
Darin Dimitrov
You're right, Jon, but - taking Darin's comment into account - at least that's the closest I can get to what I imagined. I guess that I - and my co-workers who have to use the code as well - can live with the one additional character.
Pepor
+4  A: 

The way classes in the .NET Framework handle this scenario is TryGetValue with an out parameter. The return value is an indicator of whether the get was successful, where the out parameter contains the value requested (on success) or a suitable default value (on failure).

This pattern makes the implementation very simple for reference and value types. You would only need a single method to handle both scenarios.

For an example of this pattern, see Dictionary<TKey,TValue>.TryGetValue.

Programming Hero
Good idea, but it would mean that I cannot retrieve the values in-line, which I would like to (as the example of how I want to use the code shows). I would need to declare variables and call TryGetValue() for each of the values before creating SomeObject. This makes the code much larger and less readable than I like. I was looking for a more compact and elegant solution.
Pepor
+2  A: 

An alternative solution could be this one:

public class Arguments {
    public T Get<T>(string argumentName,T defaultValue) {
        // look up and return value or defaultValue if not found
    }
}

return new SomeObject(
    args.Get<string>("Name","NoName"),
    args.Get<int>("Index",1)
);

In that particular case you would not even have to specify the generic type, as it could be inferred by the default parameter:

return new SomeObject(
    args.Get("Name","NoName"),
    args.Get("Index",1)
);
Paolo Tedesco
This looks good. Unfortunately, it is not that easy at first glance to know what the second argument means. Of course, IntelliSense provides that information, but I like my code to be easily comprehensible without having to rely on documentation. There are several thousand lines of doc comments in my code base that none of my co-workers ever bothered to even look at. ;-)
Pepor
+1  A: 

Constraints are not part of the signature. thus the answer to your question is no.

Sky Sanders