Pepor
Pepor

Reputation: 1072

Multiple generic methods with identical names and arguments, but different results and constraints

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?

Upvotes: 4

Views: 2517

Answers (5)

ghord
ghord

Reputation: 13817

Although it does not work directly due to identical argument types, You can do that by adding optional defaultValue parameter which defaults to null:

public class Arguments
{
    // lots of other code here

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

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

The reason this one works is that second argument type is different for both contraints (In the the method with class constraint it is simply T, and in the method with struct constraint it is Nullbale<T>).

Following code works as you would expect:

var args = new Arguments();

var stringValue = args.TryGet<string>("Name") ?? "NoName";

var intValue = args.TryGet<int>("Index") ?? 1;

Upvotes: 1

Sky Sanders
Sky Sanders

Reputation: 37084

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

Upvotes: 7

Paolo Tedesco
Paolo Tedesco

Reputation: 57202

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)
);

Upvotes: 3

Paul Turner
Paul Turner

Reputation: 39625

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.

Upvotes: 5

Darin Dimitrov
Darin Dimitrov

Reputation: 1038850

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];
}

Upvotes: 3

Related Questions