0

I've implemented a Maybe<T> class which implements IEnumerable in C# inspired by Mark Seemann and it works great. I've got a suite of helper extension methods that make common operations Maybe-friendly and they all work great too. I had the idea to change Maybe<T> from a class to a struct to prevent it from being nullable and it seems to work great with one problem:

var inputs = new Maybe<string>[] { /* from somewhere */ };

// This line works with class Maybe and struct Maybe
var results1 = inputs.SelectMany(x => ParseMaybe.ToInt32(x));

// This line works with class Maybe but with struct Maybe it yields a compiler error: 
// Error CS0407 'Maybe<int> ParseMaybe.ToInt32(string)' has the wrong return type
var results2 = inputs.SelectMany(ParseMaybe.ToInt32);

Note: Because Maybe implements IEnumerable here, I'm using SelectMany instead of Select.

This code works with the class version but gets the compiler error as a struct. Why?

7
  • 1
    Would you like to show us the struct and class implementation? Commented Dec 16, 2015 at 21:48
  • SelectMany is used to flatten nested collections. You should use Select here, otherwise you will get an enumeration of chars. Commented Dec 16, 2015 at 22:18
  • Youre right about the chars. In simplifying my example I lost some context. I fixed it to make it more clear. Commented Dec 16, 2015 at 22:31
  • gist.github.com/darkmyst/ecbc00e2f2f6c5f42ded is the class implementation. The struct version differs only in removing the default constructor and avoiding null refs on _value Commented Dec 16, 2015 at 22:38
  • @Charles struct are not a non nullable version of class. They are passed as value and not as reference and are boxed-unboxed when used. That can cause strange issue when use in foreach or during enumeration, i suggest you to avoid your idea to use it as non nullable version of class. If non nullable values is a must for you, try to create a static helper methd dat initialize properties (or field) of its target. Commented Dec 16, 2015 at 22:40

1 Answer 1

3

This is because struct type parameters are invariant. Even though Func<A, B> is covariant in its return type this only applies if B is a reference type. This means you cannot assign a Func<string, Maybe<int>> to a Func<string, IEnumerable<int>> even though Maybe<T> implements IEnumerable<T> e.g.

Func<string, Maybe<int>> f = ParseMaybe.ToInt32;
Func<string, IEnumerable<int>> g = f; //won't compile

In contrast the following will compile since int[] is a reference type:

Func<string, int[]> f = ???
Func<string, IEnumerable<int>> g = f;
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.