Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicitly converting a generic to a wrapper

I'd like to automatically wrap a value in a generic container on return (I am aware that this is not always desirable, but it makes sense for my case). For example, I'd like to write:

public static Wrapper<string> Load() {
    return "";
}

I'm able to do this by adding the following to my Wrapper class:

public static implicit operator Wrapper<T>(T val) {
    return new Wrapper<T>(val); 
}

Unfortunately, this fails when I attempt to convert an IEnumerable, complete code here (and at ideone):

public class Test {
    public static void Main() {
        string x = "";
        Wrapper<string> xx = x;

        string[] y = new[] { "" };
        Wrapper<string[]> yy = y;

        IEnumerable<string> z = new[] { "" };
        Wrapper<IEnumerable<string>> zz = z; // (!)
    }
}
public sealed class Wrapper<T> {
    private readonly object _value;
    public Wrapper(T value) {
        this._value = value;
    }
    public static implicit operator Wrapper<T>(T val) { return new Wrapper<T>(val); }
}

The compilation error I get is:

Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<string>' to '...Wrapper<System.Collections.Generic.IEnumerable<string>>'

What exactly is going on, and how can I fix it?

like image 900
mk. Avatar asked Aug 07 '15 19:08

mk.


1 Answers

The reason is part of the C# spec, as noted in this answer:

A class or struct is permitted to declare a conversion from a source type S to a target type T provided all of the following are true:

  • ...
  • Neither S nor T is object or an interface-type.

and

User-defined conversions are not allowed to convert from or to interface-types. In particular, this restriction ensures that no user-defined transformations occur when converting to an interface-type, and that a conversion to an interface-type succeeds only if the object being converted actually implements the specified interface-type.

Source

Your implicit conversion works when used differently, like in the following code:

using System;
using System.Collections.Generic;

public class Wrapper<T>
{
    public T Val { get; private set; }

    public Wrapper(T val)
    {
        Val = val;
    }

    public static implicit operator Wrapper<T>(T val)
    {
        return new Wrapper<T>(val); 
    }
}

public class Test
{
    public static Wrapper<IEnumerable<int>> GetIt()
    {
        // The array is typed as int[], not IEnumerable<int>, so the
        // implicit operator can be used.
        return new int[] { 1, 2, 3 };
    }

    public static void Main()
    {
        // Prints 1, 2, 3
        foreach (var i in GetIt().Val)
        {
            Console.WriteLine(i);
        }
    }
}

The specific issue you're running into is because you store your array in a IEnumerable<string> local variable before returning it. It's the type of the variable passed into the implicit operator that matters: because the source type S is IEnumerable<int> on your local variable, the operator can't be used. int[] isn't an interface, so it works.

like image 145
31eee384 Avatar answered Oct 21 '22 03:10

31eee384