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

Proposal: Implicit Interfaces #2146

Closed
gregsdennis opened this issue Apr 21, 2015 · 16 comments
Closed

Proposal: Implicit Interfaces #2146

gregsdennis opened this issue Apr 21, 2015 · 16 comments

Comments

@gregsdennis
Copy link

The Problem

Define an interface in such a way that its entire purpose is to represent a set of other interfaces.

Suppose you have two interfaces:

interface IA
{
    void SomeMethod();
}

interface IB
{
    void SomeOtherMethod();
}

Now suppose you want to create a property on an object into which you can place an object of either type. Since they have no common ancestry, you would have three options (that I've seen, you may devise your own):

  • declare the property as object and then test/cast in order to access the functionality
    class MyImpl : IA, IB
    {
        public void SomeMethod() { ... }
        public void SomeOtherMethod() { ... }
    }
    

    class MyClass
    {
    public object MyProp { get; set; }
    }

    class MyApp
    {
    static void Main(string[] args)
    {
    var myClass = new MyClass { MyProp = new MyImpl() };
    ((IA)myClass.MyProp).SomeMethod();
    ((IB)myClass.MyProp).SomeOtherMethod();
    }
    }

  • create a third interface defined as the combination of the two
    interface ICombined : IA, IB {}
    

    class MyImpl : ICombined
    {
    public void SomeMethod() { ... }
    public void SomeOtherMethod() { ... }
    }

    class MyClass
    {
    public ICombined MyProp { get; set; }
    }

    class MyApp
    {
    static void Main(string[] args)
    {
    var myClass = new MyClass { MyProp = new MyImpl() };
    myClass.MyProp.SomeMethod();
    myClass.MyProp.SomeOtherMethod();
    }
    }

  • create a proxy type which exposes a single field via independent properties
    class MyProxy<T, TA, TB> where T : TA, TB
    {
        private object _value;
    
    public TA AsA { get { return (TA)_value; } }
    public TB AsB { get { return (TB)_value; } }
    
    public MyProxy(T value) { _value = value; }
    

    }

    class MyClass
    {
    public MyProxy MyProp { get; set; }
    }

    class MyImpl : IA, IB
    {
    public void SomeMethod() { ... }
    public void SomeOtherMethod() { ... }
    }

    class MyApp
    {
    static void Main(string[] args)
    {
    var myClass = new MyClass { MyProp = new MyProxy<MyImpl, IA, IB>(new MyImpl()) };
    myClass.MyProp.AsIA.SomeMethod();
    myClass.MyProp.AsIB.SomeOtherMethod();
    }
    }

The second option is generally the more preferred option, however, it's not always doable. What if, instead of IA and IB, we use IComparableand IConvertible?
interface ICombined : IComparable, IConvertible {}

class MyImpl : ICombined
{
// IComparable implementation
public int CompareTo(object obj) { ... }

// IConvertible implementation
public int ToInt32() { ... }
...

}

class MyClass
{
public ICombined MyProp { get; set; }
}

class MyApp
{
static void Main(string[] args)
{
var myClass = new MyClass { MyProp = new MyImpl() };
var comparison = myClass.MyProp.CompareTo(new object());
var newInt = myClass.MyProp.ToInt32();
}
}


This only works for classes which specifically implement the ICombined interface. You would not be able to assign types like int, double, andstring, each of which implement both IComparable and IConvertible.

The Solution

We introduce a new usage of the implicit keyword for interfaces.
implicit interface ICombined : IComparable, IConvertible {}
This tells both the compiler and the run-time that any class which implements both IComparable and IConvertible can be interpreted as implementing ICombined.

The remainder of the code could stay the same, but now, in addition to explicit implementations of ICombined we could also assign any type which implements both IComparable and IConvertible, including int, double, and string.

class MyApp
{
    static void Main(string[] args)
    {
        var myClass = new MyClass { MyProp = 6 };
        var comparison = myClass.MyProp.CompareTo(new object());
        var newInt = myClass.MyProp.ToInt32();

        myClass.MyProp = "example";
        var newComparison = myClass.MyProp.CompareTo(new object());
        ...
    }
}

Additionally, you could use this new interface to define a collection of such objects:

var list = new List<ICombined> { 6, "string" };

Defining an interface this way, it becomes retroactive. That is, types which implement all base interfaces for the implicit interface also are said to implement the implicit one.

The Rules

  1. An implicit interface may combine any number of interfaces.
  2. An implicit interface may not define any additional functionality. That is, it must be empty.
That's really it.

Finally, the run-time will have to do some type checking, which it should do already for the is and as keywords. It wouldn't need to know all implicit interfaces that a type implements, it would just need to check as requested.

var implemented = 6 is ICombined;

This basically asks, "Does the type of 6, which is int, implement ICombined?" To determine that, it sees that ICombined is an implicit interface so it asks, "Does it implement all of the interfaces implmented by ICombined?" So it's equivalent to writing:

var implemented = 6 is IConvertible && 6 is IComparable;

Simple field and property assignments would be compiler-verifiable.

@HaloFour
Copy link

The compiler might be able to sneakily handle that within the code that it compiled itself but it couldn't affect how other assemblies might treat that type. To make the CLR recognize int as an ICombined would require changes to the runtime.

You might want to submit this as a feature request to the CoreCLR team.

@dsaf
Copy link

dsaf commented Apr 22, 2015

Is it related to intersection types?

@gregsdennis
Copy link
Author

@dsaf, not really, but I can see the proximity to that idea.

The concept behind this is closer to type verification by implementation. I remember reading about a feature that some languages have where the class is defined not by some concrete Type concept, but rather by the functionality that the class provides. By this, a class can be considered to implement an interface simply by implementing the contract it states (whereas C# requires you to explicitly state you're implementing the interface, even if the contract happens to match some other interface).

This could considered be a partial implementation of that feature, but still keeping to the ideals which already exist in C#.

@gregsdennis
Copy link
Author

@HaloFour, I was going to say that the CLR wouldn't have to change, citing that

var implemented = 6 is ICombined;

could be converted by the compiler into

var implemented = 6 is IConvertible && 6 is IComparable;

before translating to IL.

However, now that I think about it further, it would still need to have a type for any variable which holds an ICombined value. Otherwise, it would have to use object and inject a lot of casts to switch between the various interfaces when it needs to process the value. That could lead to other issues.

Regarding use in other assemblies, those assemblies would have to reference this one to get the type definition of ICombined. How would this be a problem for those assemblies?

@HaloFour
Copy link

You could handle this via a helper method that does some reflection (obviously caching the results). I actually had some incubator project some years back to provide a duck-typing helper method that would construct a concrete proxy class in a dynamic assembly that would implement the interface members and point them to compatible members of the target type. It worked nice but it had the same issues where the proxy type isn't the same as the target type and you can't convert between them. The compiler would have the same issue. If it emitted a synthetic type the instance is no longer the original type. You couldn't pass that ICombined to another method and cast it to an int.

@bondsbw
Copy link

bondsbw commented Apr 23, 2015

@gregsdennis

An implicit interface may not define any additional functionality. That is, it must be empty.

In that case it might make more sense to remove the braces:

implicit interface ICombined : IComparable, IConvertible;

@gafter
Copy link
Member

gafter commented Nov 20, 2015

I wonder if intersection types would be a better fit, since there is little reason to give a name to the combination.

@benaadams
Copy link
Member

Have lots of these types in https://github.com/dotnet/coreclr/issues/2179 that require naming for ease of use.

@Thaina
Copy link

Thaina commented Mar 9, 2016

I would prefer that we could alias and cast or check for a group of type than making new implicit interface

For solving your problem it would better if we just

using ICombined = (IConvertible,IComparable); 
if(obj is (IConvertible,IComparable))
   return obj as (IConvertible,IComparable);
var list = new List<(IConvertible,IComparable)>();

Syntax is (type,type) or maybe (type & type) and (type | type)

But it would require CLR support

@gregsdennis
Copy link
Author

@Thaina, I think your idea is good. I like the idea of declaring the shorthand in the using statement.

I think the (type | type) syntax would explain the concept better in code. It is literally "this or that type."

@HaloFour
Copy link

HaloFour commented Mar 9, 2016

The (A, B) syntax is proposed for tuples. I like (A & B) since the intersected type would have to implement both interfaces. The concept of (A | B) is interesting, a type that implements either interface and the consumer can only access shared members. Java does have limited typing like this in exception handlers, e.g. catch (IOException | SQLException exception) { }.

I wonder how much of either concept that the compiler could implement without CLRsupport. There are tricks that the compiler could do within a method, but it would have to be pretty limited.

@gregsdennis
Copy link
Author

@HaloFour, you are correct: the desired syntax for this should be (A & B). (Early morning; brain not booted.)

@aluanhaddad
Copy link

Intersection types would add a great deal of expressiveness to the language. I really want to see this implemented and I'm really happy to see renewed interest. This would allow for much more powerful and expressive consumption of generic APIs and allow for powerful, typesafe ad hoc cross axial classification of types. I think the & syntax is a good choice and has worked out very well in languages like TypeScript.

As for the question of declaring a name for some intersection type, I think the using syntax would provide that intuitively.

@Thaina
Copy link

Thaina commented Jun 6, 2016

I have seen many proposal about intersect/union type at where clause. And I think it all could be relate to trait too

Should we have some thread or tag to sum up all these kind of proposal as generic enhancement or type constraint ?

@aluanhaddad
Copy link

aluanhaddad commented Jun 24, 2016

@Thaina the problem is that while

where T: IComparable, IEquatable

works very well already for specifying intersecting type constraints, it is impossible to call such a method (with resorting to dynamic) without casting to a type implementing both interfaces. Such a definition may not be available, and if it is it is still not ideal. I would welcome a thread gathering ideas for these proposals, but it is about much more than specifying generic constraints.
Basically,
Union Types would be very useful in generic constraints.
Intersection types would be very useful for consuming generic methods and for pattern matching.

@gafter
Copy link
Member

gafter commented Mar 24, 2017

Issue moved to dotnet/csharplang #344 via ZenHub

@gafter gafter closed this as completed Mar 24, 2017
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

9 participants