3

I'm trying to update my code to use the new value tuples, and overall it's making it a lot more readable, so I love it.

However, I've ran into issues working with nullable values. In particular I have either collection entries that have to be null or functions that return null values under certain conditions. For example:

internal static TValue SafeTryGet<TKey, TValue>(this IDictionary<TKey, TValue> list, TKey key) =>
        list.TryGetValue(key, out TValue ret) ? ret : default(TValue);

This works fine with normal Tuple<> objects since they're classes, but value types will never return null which breaks this.

I've tried something like the below, but overloading resolution doesn't account for this case, it just gives a compilation error:

internal static TValue SafeTryGet<TKey, TValue>(this IDictionary<TKey, TValue> list, TKey key) where TValue : class =>
        list.TryGetValue(key, out TValue ret) ? ret : default(TValue);

internal static TValue? SafeTryGet<TKey, TValue>(this IDictionary<TKey, TValue> list, TKey key) where TValue : struct =>
        list.TryGetValue(key, out TValue ret) ? ret : new TValue?();

Short of providing a new method that does this, is there a clean way of writing this to return a nullable type when TValue is a struct? I emphasize the word clean because I can obviously code around it, but I'd like a semantic solution to the issue rather than a brute force approach.

Keep in mind that the same issue happens with the FirstOrDefault() Linq method. It won't return null, which makes it rather useless in this context. I'd like a solution that addresses this too if possible.

Edit: Please keep in mind the Linq issue. This is not a duplicate of that other post, because their requirements were more flexible. I'd like to make Linq work with value tuples as normal if possible.

aloisdg
  • 22,270
  • 6
  • 85
  • 105
Blindy
  • 65,249
  • 10
  • 91
  • 131
  • @Servy, it's not a duplicate, it's a real issue with Linq. `FirstOrDefault`, `SingleOrDefault`, etc, they all do not work with the new value tuple type unless I'm missing something obvious. – Blindy Mar 10 '17 at 18:20
  • Your code isn't using LINQ at all. You've provided two methods that don't work; the duplicate explains why they don't work, and provides different solutions for solving the problem. It's also false that those LINQ methods don't work with tuples. They don't do what you want them to do; that's very different (and also unrelated to the question you asked). – Servy Mar 10 '17 at 18:24
  • It's not unrelated, it's using the same pattern (intentionally I might add). And the wording I used is pretty clear, "the same issue happens with the FirstOrDefault() Linq method. It won't return `null', which makes it rather useless in this context". Of course they work, they just don't return any useful information. – Blindy Mar 10 '17 at 18:26
  • The fact that there exists some .NET method that was implemented differently than you wish it were implemented is in fact unrelated to your question of why your two methods don't work, or how to do what you're trying to get them to do. If you know that they work then *don't say that they don't work*. Again, they don't work *the way you wish they did*, that's different. – Servy Mar 10 '17 at 18:29
  • So I'm asking for a work around, specifically for my example, but also one that would work for Linq `*OrDefault()` functions. You can argue the scope of the question, but that aside, in my opinion it's clear, answerable and potentially useful for a lot of people. Also not a duplicate. – Blindy Mar 10 '17 at 18:31
  • But it is a duplicate. That's *exactly* what the duplicate answers. You don't actually have a different question. That you don't *like* the answer doesn't mean it's not a duplicate, it just means you don't like the answer to your question. – Servy Mar 10 '17 at 18:32
  • 4
    I reread this question several times and agree with @Servy. You mention LINQ, tuples but don't use them anywhere. Also did not understand how C# 7 is related here (yes you use one feature from C# 7 but it will be the same without). Maybe you should provide more details if you don't agree this is a duplicate. – Evk Mar 10 '17 at 18:52
  • Voting to re-open this as IMO @Servy is wrong: this is not a duplicate. The question is just poorly constructed, leading people to focus on overloaded methods, rather than the real question: how to handle `try` methods that return a tuple, and thus can't return `null` to indicate failure. One simple way of handling such a situation is to use an option/maybe type, that returns `some(tuple-value)` or `none`. – David Arno Mar 13 '17 at 09:08
  • I also do not see how your question relates to the code samples. If you use `.FirstOrDefault()` on a sequence of value types (other than `Nullable<>`), the "default" will not be null. For example for an `IEnumerable`, if `.FirstOrDefault()` is `0`, that can be either because the sequence is empty, or because it starts with a zero. Similarly if you have `IEnumerable>`, because `KeyValuePair<,>` is a value type, the default will be a pair with key `0` and value `null`. The same happens for the new C# 7 value tuples. – Jeppe Stig Nielsen Mar 13 '17 at 09:26
  • Can you write code showing exactly what you want? That is write some code that tries to call these methods showing us the kind of data structures you want `TKey` and `TValue` to be. As it is you are sounding like you are saying that `TValue` is a value type (fine) but that you want it to be able to return null which makes no sense because TValue can't be null. If `TValue` is a nullable type then it should work fine to return null. – Chris Mar 13 '17 at 09:30
  • @DavidArno So you reopened a question that's an exact duplicate of another question because you simply didn't understand what the question was asking? It was asking *literally exactly the same thing as the duplicate question*. The question is *specifically* asking how to have two overloads, but each with a different generic constraint. Your summation of the question isn't correct; it's not asking how to use a Maybe type; C# already has `Nullable`; it wants to have two overloads to the same method, one that returns `T` and one that returns `Nullable`. The duplicate answers that. – Servy Mar 13 '17 at 13:24
  • 1
    @Servy OK, my mistake. I read the question differently, but replies to answers show you were right in your interpretation. So re-closed. – David Arno Mar 13 '17 at 16:29

2 Answers2

3

As for the link issue, that is easily solved:

IEnumerable<int> values = ...

int? fod = values.Select(i => (int?)i).FirstOrDefault();

Note that this not only works for IEnumerable<T>, but also for IQueryable<T>.

As for the more general quesion, that is indeed answered by Generic constraints, where T : struct and where T : class

Community
  • 1
  • 1
Kris Vandermotten
  • 10,111
  • 38
  • 49
  • This doesn't solve the problem that the questions asks how to solve. The OP isn't looking to get the first item from a sequence. – Servy Mar 13 '17 at 13:25
0

It is not clear how your question is related to your examples.

You can make an extension for IEnumerable<> of any type (value type or reference type elements) like this:

public static bool TryGetFirst<T>(this IEnumerable<T> source, out T first)
{
  var oneOrEmpty = source.Take(1).ToList();
  if (oneOrEmpty.Count == 1)
  {
    first = oneOrEmpty[0];
    return true;
  }
  first = default(T);
  return false;
}

or with a slightly better performance (I would think):

public static bool TryGetFirst<T>(this IEnumerable<T> source, out T first)
{
  using (var e = source.GetEnumerator())
  {
    if (e.MoveNext())
    {
      first = e.Current;
      return true;
    }
  }
  first = default(T);
  return false;
}

Here you use the returned bool to see whether there was a first element, and you check the out parameter to see what that first element was in that case.

This can be useful for sequences of reference types as well, if the sequence might contain a null value (as its first member).


Otherwise, for value types only, you can steal the trick from Kris Vandermotten's answer and put it into an extension method:

public static T? TryGetFirst<T>(this IEnumerable<T> source) where T : struct
{
  return source.Select(t => (T?)t).FirstOrDefault();
}

In this case a return value of null means there was no first element.

Sefe
  • 13,731
  • 5
  • 42
  • 55
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181