Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implicit interface conversion #14186

Closed
Opiumtm opened this issue Sep 29, 2016 · 17 comments
Closed

Implicit interface conversion #14186

Opiumtm opened this issue Sep 29, 2016 · 17 comments

Comments

@Opiumtm
Copy link

Opiumtm commented Sep 29, 2016

Today's implementation of implicit conversion operators doesn't allow interfaces to participate - only abstract classes, which isn't always possible or desirable (as interface is generally better than abstract class if you're planning your type hierarchy).

Why it's important? It allow to use adapter pattern transparently. You can write or generate (using upcoming "source generators" feature) adapter for certain interface and if implicit interface conversion is possible you could simply omit explicit adapter instantiation and use classes (which have adapter for certain interface + implicit conversion operator to certain interface) just in place of adapted interface.

It's all possible if you use abstract classes (implicit conversion operators are only applicable for classes and structs), but not for interfaces. It's a huge miss as many people (including me) prefer to use interfaces instead of abstract classes to handle polymorphism when possible.

How "extension everything" upcoming language feature would handle implicit interface conversion case?

@DavidArno
Copy link

I don't think the "extension everything" feature will, but a feature that is part of pattern matching (but which isn't being released with C# 7) will address this. A new operator, is was/will be added to the language such that you could define:

public static bool operator is(AdapterType adapter, out AdaptedType original)
{
    original = convert from adapter to original;
    return true if conversion works;
}

And it can then be used like:

if (original is AdapterType adapter)
{
    // user adapter here
}

@HaloFour
Copy link

That prohibition is intentional in order to avoid the ambiguity in attempting to resolve an assignment between a conversion and a simple cast. The conversion operators are intended to bridge between two types which are otherwise incompatible with one another. The spec further explicitly prohibits redefining existing conversion operations, which would include downcasts.

@gulbanana
Copy link

would it be safe to support only implicit conversion from interfaces?

@whoisj
Copy link

whoisj commented Oct 3, 2016

I just hit this issue, and the restriction is making life rather difficult.

We've decided that, where possible, the library limits public types to interfaces. The unfortunate part is that classes cannot implement public static explicit operator IThing(Thing value) because of "reasons". I understand the difficulty of deciding which cast operator to use, but not allowing explicit casts is ridiculous.

Here's the actual scenario. We have two interfaces ITypeA and ITypeB, and a class which implements both class ClassAB : ITypeA, ITypeB. References to objects of ClassAB are given back as references to ITypeA. However, despite the reference is really typeof(ClassAB) any attempted to itypeBref as ITypeB always results in null. Makes very little sense.

@svick
Copy link
Contributor

svick commented Oct 3, 2016

@whoisj What you're describing should work. Are you sure there isn't something else going on in your code?

@whoisj
Copy link

whoisj commented Oct 3, 2016

Are you sure there isn't something else going on in your code?

No. This is a large code base, there could be other things happening. However, it seems consistent to me. Perhaps there's some hierarchy of interface inheritance that is causing issues?

@HaloFour
Copy link

HaloFour commented Oct 3, 2016

@whoisj

That's not the behavior of C#. The type of the instance doesn't change depending on the type of the variable. Sounds like that project is wrapping those instances, either directly or via proxy, so that the instance you are working with are actually not instances of ClassAB.

@theunrepentantgeek
Copy link

theunrepentantgeek commented Oct 11, 2016

I've run into this as well - from a different angle.

Playing around quite some time ago, I had defined an interface IOption<T> with two implementations, Some<T> and None<T>, and wanted to define a implicit cast to "wrap" a T into a Some<T>.

Essentially, I had this code:

public IOption<Customer> FindCustomer()
{
    Customer c = _database.FindCustomer();
    return new Some<Customer>(c);
}

and I wanted to be able to write this:

public IOption<Customer> FindCustomer()
{
    Customer c = _database.FindCustomer();
    return c;
}

which requires both defining an implicit cast from T to Some<T> and having the compiler recognize that this would be satisfactory.

@whoisj
Copy link

whoisj commented Oct 12, 2016

@theunrepentantgeek that is basically identical to what I was attempting to express, but significantly more clear and less confusing. Thanks 😄

@hrumhurum
Copy link

hrumhurum commented Mar 9, 2018

I've run into this issue as well:

 struct Optional<TValue>
    {
        public Optional(TValue value)
        {
            _Value = value;
            _HasValue = true;
        }

        TValue _Value;
        bool _HasValue;

        public TValue Value
        {
            get
            {
                if (!_HasValue)
                    throw new Exception("Optional value not present.");
                return _Value;
            }
        }

        public bool HasValue => _HasValue;

        public static implicit operator Optional<TValue>(TValue value) => new Optional<TValue>(value);
    }

The usage scenarios:

Optional<int> a = 0; // works

Optional<string> b = null; // works

System.IO.Stream stream = null;
Optional<System.IO.Stream> c = stream; // works

IDisposable disposable = null;
Optional<IDisposable> d = disposable; // DOES NOT WORK

Is there any ongoing work to solve this in future C# versions? Please note that Optional<TValue> has no inherited interfaces, so there is no ambiguity between conversion and assignment operators possible.

@thepinkmile
Copy link

I am wondering if this is also something I have recently had problems with...
I am writing some new extensions to MS.Extentions.DI to allow creating tenanted services and providing a MultiTenantServiceProvider.
To be able to provide the correct constructor injection I have a wrapping type that is derived from in a tenant to provide relevant tenant specific lookup of registered services.

My wrapper looks something like this:

public class ServiceWrapper<T>
{
    public ServiceWrapper(IServiceProvider services)
    {
        _services = services;
    }

    public T GetService()
    {
        return (T)_services.GetService(typeof(T));
    }

    public static implicit operator T(ServiceWrapper<T> wrapper)
    {
        return wrapper.GetService();
    }

     private readonly IServiceProvider _services;
}

My consumer looks something like this:

public class ServiceConsumer<T>
{
    public ServiceConsumer(ServiceWrapper<T> service)
    {
        Service = service;
    }

    public readonly T Service;
}

I have a set of NUnit tests (using NSubstitute & FluentAssertions) that looks something like this:

using System;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using NUnit.Framework;

namespace ImplicitWrapperConversions
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void Should_implicitly_convert_wrapper_in_constructor()
        {
            var dummy = Substitute.For<TestInterfaceImpl>();
            ServiceBuilder
                .AddTransient(typeof(ServiceWrapper<>))
                .AddTransient(typeof(ServiceConsumer<>))
                .AddSingleton<ITestInterface>(dummy)
                ;
            ServiceConsumer<ITestInterface> consumer = null;

            Assert.DoesNotThrow(() => consumer = Services.GetService<ServiceConsumer<ITestInterface>>());

            consumer.Should().NotBeNull();
            consumer.Service.Should().Be(dummy);
        }

        [Test]
        public void Should_implicitly_convert_wrapper_inline()
        {
            var dummy = Substitute.For<TestInterfaceImpl>();
            ServiceBuilder
                .AddTransient(typeof(ServiceWrapper<>))
                .AddTransient(typeof(ServiceConsumer<>))
                .AddSingleton<ITestInterface>(dummy)
                ;
            var consumer = Services.GetService<ServiceConsumer<ITestInterface>>();
            ITestInterface service = null;

            Assert.DoesNotThrow(() => service = consumer);

            service.Should().Be(dummy);
        }

        #region Support

        [SetUp]
        public void FixtureSetup()
        {
            ServiceBuilder = Substitute.ForPartsOf<ServiceCollection>();
        }

        private IServiceCollection ServiceBuilder;

        private IServiceProvider Services => ServiceBuilder.BuildServiceProvider();

        #endregion
    }

    #region TestTypes

    internal interface ITestInterface
    {
    }

    internal class TestInterfaceImpl : ITestInterface
    {
    }

    #endregion

In my test "Should_implicitly_convert_wrapper_inline" inside the Assert.DoesNotThrow(...) I have a red squiggle and compiler error.
My question is: How come the ServiceConsumer class is able to use the implicit operator and when I do it directly in code it doesn't work?
This would be a handy feature to simplify a lot of my code as in my real code I have a few places where I manually get Tenant Services based on a tenant name (which is similar to how "Should_implicitly_convert_wrapper_inline" works). Currently I am having to have a non-DI singleton to keep track of all ServiceWrapper<T> implementations and do a lookup on this and create a generic type to then call the ServiceProvider with.
All of this seems like ridiculous boiler-plate code to me, which could be removed.

I could be wrong that this is related to this issue, but it seems very close to me and I couldn't find anything else similar that matched my issue. However I didn't want to create a duplicate if this issue is close enough.

Any thoughts welcome on this.
It is probably me just being naive or missing something really simple.

@adnanalbeda
Copy link

I came across this problem today, three years after beginning this issue here.
Well, Microsoft has lunched preview version that makes interfaces have default implementation or values, I am not sure whether this problem would remain after publishing this feature.

@petriashev
Copy link

This feature can reduce annoying code. Is there any plans to implement it?

@biiiipy
Copy link

biiiipy commented Aug 17, 2020

I'm also interested in this. This seems like an arbitrary limitation.

@daiplusplus
Copy link

daiplusplus commented May 19, 2021

I'd prefer it if C# supported implicit-conversion and explicit-conversion through an interface that's part of the BCL, instead of through only the static implicit operator approach, that way we could use conversions in generic code (as generic constraints can use interfaces, but cannot require static members) as well as reduce boilerplate type conversion code.

What I'd like to see is something like this as a standard interface:

namespace System
{
    // For representing domain identity, even when object-reference-identity cannot be maintained due to multiple-inheritance being disallowed.
    public interface IAm<T>
    {
        T Self { get; }
    }

    // For conversion that will always succeed.
    public interface IConvertTo<T>
    {
       T Convert();
    }

    // For conversions that might possibly fail.
    public interface IMaybeConvertTo<T>
    {
        Boolean TryConvertTo( [NotNullWhen(true)] out T converted );
    }
}

This would be useful in many cases and conveniently side-steps Eric Lippert's discussion about not wanting user-defined conversions to replace built-in conversions that preserve object-identity.

But this could be made even better by adding a new C# keyword is-maybe which is aware of these interfaces and so performs the exhaustive QueryService-esque check for us:

if( something is-maybe AnotherType another )
{
    DoStuff( another );
}

Where the is-maybe operator is equivalent to:

{
    if( something is AnotherType a )
    {
        DoStuff( a );
    }
    else if( something is IAm<AnotherType> b )
    {
        DoStuff( b.Self );
    }
    else if( something is IConvertTo<AnotherType> c )
    {
        DoStuff( c.Convert() );
    }
    else if( something is IMaybeConvertTo<AnotherType> d && d.TryConvertTo( out AnotherType e  ) )
    {
        DoStuff( e );
    }
}

Right now I implement this is-maybe operator as a generic extension method (and define those interfaces in my own private utility library), but because we can't have custom control-structures (i.e. force-inlined lambdas and non-local returns), and because we can't have implicit conversions to/from interfaces, it's just as difficult and ugly to use as the inadequate code it's meant to replace...


And it could be made even better by adding a postfix "try-convert" operator for succint parameter conversion through the above interfaces without needing to muddy our code with explicit calls to conversion methods.

@HaloFour
Copy link

@Jehoel

I'd prefer it if C# supported implicit-conversion and explicit-conversion through an interface that's part of the BCL, instead of through only the static implicit operator approach, that way we could use conversions in generic code (as generic constraints can use interfaces, but cannot require static members) as well as reduce boilerplate type conversion code.

The runtime and C# teams are currently exploring the possibility of interfaces having static members, one of the primary use cases being to support operators for generic math, but it also would apply to generic conversion operators. This is a part of a larger project called "shapes"/"roles" which expands on the use of interfaces as generic constraints even where the generic type argument doesn't implicit the interface directly.

But this could be made even better by adding a new C# keyword is-maybe which is aware of these interfaces and so performs the exhaustive QueryService-esque check for us:

The is operator is already conditional, and there are proposals to add custom patterns to the language that would allow one type to match the pattern of another type: dotnet/csharplang#1047

I think that would cover the cases you described.

@CyrusNajmabadi
Copy link
Member

Closing this out. We're doing all language design now at dotnet/csharplang. If you're still interested in this idea let us know and we can migrate this over to a discussion in that repo. Thanks!

@CyrusNajmabadi CyrusNajmabadi closed this as not planned Won't fix, can't repro, duplicate, stale Nov 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests