dynamic interface Proposal. #2499
Replies: 26 comments
-
Beta Was this translation helpful? Give feedback.
-
@HaloFour wow didnt see those Also similar to #5306 I guess one could create a dynamic interface that inherits from a regular interface to save re-typing. public interface IFoo
{
string Bar {get;set;}
}
public dynamic interface dIFoo : IFoo {} |
Beta Was this translation helpful? Give feedback.
-
Duck typing would be a great feature ... but you are asking for it in the wrong place. The CLR would need to support it first for it to work properly, as such type checking is a runtime function as much as a compile-time one. |
Beta Was this translation helpful? Give feedback.
-
Oh, in that case, #7844. The problem is awkward runtime support. The C# compiler couldn't affect the actual interfaces implemented by an arbitrary type. The closest the compiler could get is to emit a wrapper class that implements the interface and dispatches the calls to the target instance. But it would be impossible to cast that back to the original target type. To the CLR, the target type could never actually implement that interface. The I think that structural typing would be interesting and useful, but without support for it through the CLR, alongside static typing, it wouldn't be a very good experience. |
Beta Was this translation helpful? Give feedback.
-
Could do some possibly horrible code generation expansion and use generics, struct redirection and Convert it to something like? struct DynamicFooExisting : IFoo
{
Existing _foo;
string IFoo.Bar
{
get { return _foo.Bar; }
set { _foo.Bar = value; }
}
public static implicit operator DynamicFooExisting(Existing foo)
{
return new DynamicFooExisting() { _foo = foo };
}
}
struct DynamicFooAnother : IFoo
{
Another _foo;
string IFoo.Bar
{
get { return _foo.Bar; }
set { _foo.Bar = value; }
}
public static implicit operator DynamicFooAnother(Another foo)
{
return new DynamicFooAnother() { _foo = foo };
}
}
public void TestBar<T>(T foo) where T : IFoo
{
var str = foo.Bar;
Console.WriteLine($"Bar = {str}");
}
public void Test()
{
var ex = new Existing();
TestBar((DynamicFooExisting)ex);
var an = new Another();
TestBar((DynamicFooAnother)an);
} Wouldn't work with |
Beta Was this translation helpful? Give feedback.
-
@benaadams Even if the C# did something that awful it wouldn't help how any of the rest of the run time would see the target type or the wrapper type. You'd still be forced to box the wrapper in order to pass it to any arbitrary method expecting an instance of that interface and invocation of any member methods would still have to be through |
Beta Was this translation helpful? Give feedback.
-
@HaloFour the rest of the runtime and reference assemblies wouldn't accept the dynamic interface as they wouldn't even know about it. If you converted all uses from And accepted a dynamic interface couldn't be cast to something else; it could all be done compile time. |
Beta Was this translation helpful? Give feedback.
-
So, the feature couldn't extend beyond your own assembly and it also requires you to make specific concessions as to how you work with these instances? Why aren't you just implementing the interface then? Not to mention, that type disparity will still exist. Use of Sounds like a lot of work for some really brittle voodoo. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour it would extend to assemblies referencing you, but not assemblies you referenced. So if I had a method that wanted an object that wrote bytes could have an interface that was dynamic interface IWriter { void Write(byte[] buffer, int index, int count) } and anything that used that interface could happily take a Stream, BinaryWriter or BlobWriter even though none of them are derived from each other. Not being able to cast should be fine as the point is to specify everything you need in the interface. If you need more for a different variant, then inherit the interface and create a new function and go via type overloading rather than if statements. (though should be able to cast up the dynamic interface chain) |
Beta Was this translation helpful? Give feedback.
-
You could even rewrite So dynamic interface IWriter { void Write(byte[] buffer, int index, int count); } becomes interface IWriter
{
void Write(byte[] buffer, int index, int count);
object Value { get; }
} And public void TestBar(IWriter w)
{
var s = w as Stream;
if (s != null)
{
... becomes void Test<T>(T w) where T : IWriter
{
var s = w.Value as Stream;
if (s != null)
{
... |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Lack of CLR support is a bummer. Good to know that the idea I've been kicking around for years, others have been thinking of it too. I use only C# so its good to know other languages have this feature already. I hope since other languages do this it could eventually be adopted in the CLR. Perhaps the only way to get the CLR to consider this support is to have the Roslyn team want it. We need to convince the Roslyn team that this is a killer feature for the upcoming Pattern Matching with a ton of other benefits. Then they would have more pull with the CLR. |
Beta Was this translation helpful? Give feedback.
-
@MiddleTommy If you going to achieve same results in source code - you write similar boilerplate code, simply invoking members of the wrapped object. It essentially is an application of adapter pattern public dynamic interface IFoo
{
string Bar { get; set;}
}
public class Class1
{
public string Bar { get; set; }
}
public class Class2
{
public string Bar { get; set; }
}
public static class Test
{
public static void Test(IFoo src)
{
Console.WriteLine(src.Bar);
}
public static void Test()
{
var c1 = new Class1();
var c2 = new Class1();
Test(c1);
Test(c2);
}
} would translate to [DynamicInterface] // needed for compiler to distinct between regular and dynamic interfaces
public interface IFoo
{
string Bar { get; set;}
}
[DynamicInterfaceWrapper(typeof(IFoo), typeof(Class1))]
internal sealed class DynamicInterfaceWrapper_Class1 : IFoo
{
private readonly Class1 _wrappedObj;
public DynamicInterfaceWrapper_Class1(Class1 wrappedObj)
{
_wrappedObj = wrappedObj;
}
public string Bar
{
get
{
return wrappedObj.Bar;
}
set
{
wrappedObj.Bar = value;
}
}
}
[DynamicInterfaceWrapper(typeof(IFoo), typeof(Class2))]
internal sealed class DynamicInterfaceWrapper_Class2 : IFoo
{
private readonly Class2 _wrappedObj;
public DynamicInterfaceWrapper_Class2(Class2 wrappedObj)
{
_wrappedObj = wrappedObj;
}
public string Bar
{
get
{
return wrappedObj.Bar;
}
set
{
wrappedObj.Bar = value;
}
}
}
public static class Test
{
// Unchanged! As dynamic interface is a regular interface on CLR-level
public static void Test([DynamicInterface] IFoo src)
{
Console.WriteLine(src.Bar);
}
public static void Test()
{
var c1 = new Class1();
var c2 = new Class1();
Test(new DynamicInterfaceWrapper_Class1(c1));
Test(new DynamicInterfaceWrapper_Class2(c2));
}
} So dynamic interface implementation require compiler to generate assembly-wide internal wrapper classes (re-used in same assembly) which simply invoke wrapped object members. |
Beta Was this translation helpful? Give feedback.
-
@gafter @Opiumtm @MiddleTommy I wrote a language for .NET, which simplifies a lot of syntax and hopes to be a .NET tool that is more suitable for getting started. Regardless of whether this proposal has the opportunity to be promoted, at least I have to declare my support for this feature. E.g:
|
Beta Was this translation helpful? Give feedback.
-
This looks like a duplicate of #164 to me. |
Beta Was this translation helpful? Give feedback.
-
@bbarry |
Beta Was this translation helpful? Give feedback.
-
I have a feeling that it falls under the umbrella with shapes/type-classes/extension-interfaces/whatever-its-called-today. Does your language transpile to C#? If not I'm not sure how these proposals would benefit your language. You could follow whatever IL generation lead the C# team decides to do, but even if CLR changes end up on the table to help in facilitating this I doubt it will ever be as transparent as just passing some arbitrary reference which does not implement an interface to a method that expects an instance of that interface. I expect the compiler will need to be involved at some level to generate a bridge/witness type. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Regardless of what it is called, it is increasingly a feature that can have a significant impact on C#, and its importance is not lower than the generics of the year. I sincerely hope that one day I will hear exciting news. Maybe this process has to be promoted by everyone who has this desire. My language is currently translated to C#, I think it is not ready to translate to IL, it still needs to be grammatically tested, preferably the opinions of C# users. If you are interested, please feel free to browse the grammar of this language. I will be very happy if I can receive your opinion. |
Beta Was this translation helpful? Give feedback.
-
I personally don't think "dynamic interfaces" is the right description as the feature as described isn't about introducing new kinds of interfaces but instead about ways of taking existing interfaces and existing types and defining the extensions that allows that type to appear to implement that interface. "Extension implementation" is probably a better term, although exactly what it's called is a lot less relevant to what it actually does. I think this is the most recent exploration on the subject: #1711 This is all very early in design, though, so it could go in any direction. |
Beta Was this translation helpful? Give feedback.
-
Is this a very different concept from duck typing? 🤔 |
Beta Was this translation helpful? Give feedback.
-
@HaloFour In fact, we just want a duck type that supports static checking, no matter what its name is. We want this feature if we want to get the benefits of a duck type without losing the benefits of a strong type. This may be a trend, more and more people will consider this, I am very concerned about the subsequent development. |
Beta Was this translation helpful? Give feedback.
-
Then Shapes is 100% what you want and you should keep an eye out for what's gonna happen in that space. |
Beta Was this translation helpful? Give feedback.
-
@Joe4evr |
Beta Was this translation helpful? Give feedback.
-
Yes. This is a good thing.
"Extension implementation" solves both, and has several other distinct advantages.
It's also possible that if the type exposes all of the members needed to implement a given interface that the "witness type" can automatically be created by the compiler, which effectively covers duck typing as well. If the team can enable more scenarios that's something that is definitely in their interest. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour I was reading up on #1711 -- a very fascinating discussion with lots of great ideas. I am not too thrilled about the concept of witness types though, unless they could be implemented as free abstractions. Until we can get the JIT improvements to cast an interface implemented on struct without boxing, I am assuming the witness will need to be allocated. It is probably worth the trade off even if an allocation is required. However, what's happening there is essentially an automatically generated implementation of a decorator pattern and there will be a bit of a performance trade off due to the extra layer of indirection (two pointers instead of one). While I have wanted duck typing for a while (which doesn't necessarily require a witness), the concept of being able to attach arbitrary interfaces to existing classes via extensions is a very cool concept -- it does seem to require something like a witness since there could be multiple extensions that apply the same interface with a different implementation. So the witness is required to disambiguate, in principle. So in that case, the witness is basically an implementation of the classic decorator pattern. I would presume that one could even override a class' default implementation for an interface to use an extension instead with an appropriately configured witness (not sure what the syntax would look like -- maybe the interface extension would be invoked explicitly in that case). |
Beta Was this translation helpful? Give feedback.
-
@kulics to solve the problem for your transpiled language in the meantime, you can just generate decorated classes to take whatever input and implement whatever interface on the decorator and inject the user defined logic into the decorator's interface implementation. Easy peasy. No fundamental changes are needed for C# to pull that off. (You could use the same approach even for emitting LLVM or CLI op code.) Most language syntax is just a sugary coating around design patterns anyway. Bonus: depending on your use cases, you may be able to generate struct decorators instead of classes when you can avoid boxing. However, theoretically the JIT will get better about avoiding boxing in that case in the future. |
Beta Was this translation helpful? Give feedback.
-
The witness is a struct, so there is no additional allocation. They also only contain pass-through methods to the real members, which are very easily inlined by the JIT. That approach was chosen specifically because it should be a zero-cost abstraction. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I have always thought duck typing was a feature worth looking at. I understand how it doesn't work well in a typed language and quickly becomes like javascript. The dynamic keyword was the compromise but that looses compiler checking and allows simple type checking errors. I would like to propose a new Feature I call dynamic interfaces:
The syntax would be like this
dynamic interface IFoo{...}
The difference between a standard interface and a dynamic interface is that a dynamic interface never gets implemented. It is mainly used by the compiler and intellisense.
Example:
Where this could really get interesting is with Extension Methods. You could extend objects based on their Methods and Properties instead of their class inheritance.
This also fits nicely with Pattern Matching. It would allow you to pre-define your Pattern with a dynamic interface and then simply use the "is" operator in a switch statement. This would take pattern matching to the next level.
Beta Was this translation helpful? Give feedback.
All reactions