5

I've this code :

public async static Task<T?> RequestValue1<T>(Command requestCommand)
                    where T : struct 
{
    // Whatever
}

public async static Task<T> RequestValue2<T>(Command requestCommand)
                    where T : class 
{
    // Whatever
}

I want to have the same name for my two methods. Is this even possible ?

My problems:

  • I have to write two different methods because of the return type (I want it to be null if the request failed or a value if the request succeed) which is Nullable<T> if T is a value type, and an instance of T if T is a reference type.
  • async doesn't allow ref/out, so without a method argument of type T, T isn't inferred and my two methods cannot have the same name (signature conflict, as generic constraints doesn't works for signature conflict resolution if T isn't inferred)

Currently this code works but I don't like this strange function calls between "RequestValue1" and "RequestValue2".

Nicolas Voron
  • 2,916
  • 1
  • 21
  • 35
  • @Ryan No. I try this, but `Nullable` doesn't compile if I do not set the generic constraint where `T` : struct on `T`... – Nicolas Voron Dec 19 '12 at 14:46
  • 2
    http://msdn.microsoft.com/en-us/library/ms173114.aspx: "A return type of a method is not part of the signature of the method for the purposes of method overloading." - But how about giving another input parameter to differentiate? – J. Steen Dec 19 '12 at 14:49
  • 5
    You could give them *better* names, if not the same name, such as `RequestClass` and `RequestStruct` rather than `1` and `2`. – Servy Dec 19 '12 at 14:49
  • Can you not always call the `RequestValue2` variant, but with type parameter `int?`, `double?` etc. rather than `int`, `double` etc.? – Rawling Dec 19 '12 at 14:50
  • @Servy, yes, it's what's I'm going to do if I receive no answer from this question. This question was to know if I don't miss something :) – Nicolas Voron Dec 19 '12 at 14:52
  • @Rawling, already try this, but `Nullable` is... a struct :'( So it doesn't work ! – Nicolas Voron Dec 19 '12 at 14:53
  • @NicolasVoron If you followed that convention you would have one method without restricting it to either a class or a struct. – Servy Dec 19 '12 at 14:53
  • Edit: reinstating comment about drop the `:class`, use `: new()` or even nothing in its place. – Rawling Dec 19 '12 at 14:56
  • @Rawling, Oh, OK now I see. Good approach, but I prefer to simplify the use of my function (what about the use of simple `int` for `T` this case ?) – Nicolas Voron Dec 19 '12 at 14:58
  • @Rawling It's an `async` method. So the "real" return type is `T`, not `Task` after "`await`" – Nicolas Voron Dec 19 '12 at 15:01
  • @NicolasVoron Yup, got there, I'm going to stop editing comments now :) – Rawling Dec 19 '12 at 15:02
  • @Rawling :) `new()` is a good idea too, but it exclude `string` :( – Nicolas Voron Dec 19 '12 at 15:04

3 Answers3

4

You could create your own Option type and use that to indicate if a value is returned or not:

public async static Task<Option<T>> RequestValue<T>(Command requestCommand) {
  ...
}

Update: the intent of the Option<T> type is to replace nulls and Nullable<T>s, but if you'd still like to use them, you can use these extension methods to bridge the gap:

public static class OptionExtensions {
  public static T? GetNullableValue<T>(this Option<T> option) where T : struct {
    return option.HasValue ? (T?)option.Value : null;
  }
  public static T GetValueOrNull<T>(this Option<T> option) where T : class {
    return option.HasValue ? option.Value : null;
  }
}
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • Good idea. It's a shame that it can not be one-lined this way (await for `Option` and then get `Option.Value`), because this call have to be repeated many times. – Nicolas Voron Dec 19 '12 at 15:32
  • @NicolasVoron: You'd only get `Option.Value` if `Option.HasValue`. – Jordão Dec 19 '12 at 15:35
  • @NicolasVoron: Also, what's the problem with repeating the call many times? I didn't quite get it. – Jordão Dec 19 '12 at 15:39
  • Hmm... was trying to compare `int? i = await RequestValue(Command requestCommand)` and `Option oi = await RequestValue(Command requestCommand); int? i = (oi != null) ? oi.Value : null`. Not a real problem, just a remark. ;) – Nicolas Voron Dec 19 '12 at 15:42
  • Ahh, get it! It would be better if you could work with the `Option` type as long as you can, and not work with `null`s at all. That also means you wouldn't work with `Nullable`. You're actually replacing `Nullable` with a more general concept. – Jordão Dec 19 '12 at 15:59
  • 1
    I think this is definitively the right way to go (extension method gives back the flexibility that have been lost by the wrapping). Thanks a lot Jordão ! :) – Nicolas Voron Dec 19 '12 at 17:13
1

You can drop the constraint, and make the callers pass a Nullable<T> type to your method (i.e. call RequestValue<int?>(cmd) instead of RequestValue<int>(cmd)). You can ensure nullability at runtime, like this:

public async static Task<T> RequestValue<T>(object arg) {
    var t = typeof (T);
    if (!t.IsClass && (!t.IsGenericType || t.GetGenericTypeDefinition() != typeof(Nullable<>))) {
        throw new ArgumentException("T");
    }
    // Whatever
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    If you always pass a nullable type in then you can just use `default(T)` in place of `null`. If someone wants to use a non-nullable type...it shouldn't be the end of the world. – Servy Dec 19 '12 at 15:05
  • @Servy, it's actually "the end of the world" for me, because this method is non - deterministic (using non-stable communication link), so it's important for me to know if the request fail. So the default(T) isn't enough. – Nicolas Voron Dec 19 '12 at 15:07
  • @Servy You are right, using `default(T)` is better than setting the `null` explicitly. The only downside to this solution is that it shifts the type check from compile time to runtime. – Sergey Kalinichenko Dec 19 '12 at 15:07
  • @NicolasVoron If you use the check as above, passing non-nullable `T` would trigger an exception, ensuring that "the end of the world" comes early. Once you past that check point, you are free to set `default(T)`, which is always going to be `null`. – Sergey Kalinichenko Dec 19 '12 at 15:10
  • @NicolasVoron The idea is that you'd use `default(T)`, but the caller would just know to always use a nullable type as long as it's important to know if the request succeeds, and if the caller *doesn't* need to know with 100% certainty if the default value is a real default value or not they don't need to use a nullable type. – Servy Dec 19 '12 at 15:12
  • @dasblinkenlight Yes, I saw that. Just answering to Servy which say that's this checking isn't relevant because using default(T) (which can be non-null) is enough. – Nicolas Voron Dec 19 '12 at 15:14
  • @Servy I agree with you. But don't underestimate the "very-bad-coder-which-do-not-understand-nothing-and-then-asks-me-questions" side of the force ;) – Nicolas Voron Dec 19 '12 at 15:15
  • 1
    @NicolasVoron If you want it to be idiot proof then just use two method names; it's that simple. You shouldn't be looking for any other solution if you assume a particularly stupid or malicious user. – Servy Dec 19 '12 at 15:19
  • @Servy I'll think about it – Nicolas Voron Dec 19 '12 at 15:25
0

One can have method signatures like:

    static void test1<T>(Nullable<T> param) where T:struct;
    static void test1<T>(T param) where T:class;

without conflict. Attempting to pass a non-nullable struct type will fail, but the compiler will have no problem selecting an overload given a parameter of either a nullable type or a class type.

Your scenario is a little different, since you aren't passing a parameter of a particular type; you're just trying to pass the type itself. Without an actual parameter of the type, the compiler won't be able to decide that a Nullable<T> is a better fit for one method than the other. My inclination would be to offer a method which uses some other means than the nullity of the result to indicate success or failure. The normal Try pattern would look like:

static bool RequestValue1<T>(Command requestCommand, out Task<T> Result);

I don't especially like that pattern, since it's impossible for Result to be covariant or participate in type inference. An alternative would be:

static Task<T> RequestValue1<T>(Command requestCommand, out bool Success);

Such a form would have no problem with covariance. An alternative form would be something like:

static Task<T> RequestValue1<T>(Command requestCommand, out ResultStatus Status);

where ResultStatus would be a type with a Succeeded method that returns True in case of success, but could have other members that explain what's wrong in case of failure. If it's an immutable abstract type which defines a singleton Success instance for use when things work, it could be extended in future to provide an arbitrary level of detail when things don't work without causing any GC pressure when they do.

Unfortunately, even the forms where the out parameter type doesn't depend upon T can't be used in some contexts. To allow for that, one could define a struct type CommandResult<T> which combines a T with a success indicator, in a fashion conceptually similar to Nullable<T> but without annoying struct constraint on its parameter type. The success indicator could either be a bool, or could be a status indicator as described above.

supercat
  • 77,689
  • 9
  • 166
  • 211