Replies: 2 comments 1 reply
-
A lot of this feels like shapes, except for maybe this auto-creation of alternate versions of APIs. There's a lot to unpack there, but IMO trying to auto-generate an async or cross-process version of |
Beta Was this translation helpful? Give feedback.
-
An example of what the transformations should look like: interface I
{
void A();
}
struct S : I
{
public int F;
public int P { get; set; }
public void A() {}
public void B() {}
public static void C() {}
}
/* Auto-generated */
// S in these examples means specializing for the concrete struct S above
interface Interface<S> : I
{
ref int F { get; }
int P { get; set; }
void B();
}
struct Wrapped<S> : Interface<S>
{
public S Value;
public ref int F => ref Value.F;
public int P { get => Value.P; set => Value.P = value; }
public void A() => Value.A();
public void B() => Value.B();
}
interface StaticInterface<S>
{
void C();
}
struct StaticWrapped<S> : StaticInterface<S>
{
public void C() => S.C();
}
class Boxed<S> : Interface<S>
{
private S value;
public S Value => value; // value can modified only by its methods, not overwritten by another value
public ref int F => ref value.F;
public int P { get => value.P; set => value.P = value; }
public void A() => value.A();
public void B() => value.B();
}
record Snapshot<S>
{
public int F { get; init; }
public int P { get; init; }
} The idea is not being able to specialize generic types for certain arguments, but to provide a mechanism that can generate those types, and standardize the type templates used above. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This is not a fully fleshed-out proposal, rather a discussion about how to best address a certain area of issues regarding automation of type transformations.
I shall start with a motivation example of remoting. One of what I view as a part of its issues was the lack of greater control over the course of operations. Invoking a method on a remotable object could mean sending a request and waiting for response from a distant server, which could be seconds apart. Yet in many cases that is not really needed to be provided as inseparable sequential operations – rather it makes sense to send a couple of requests at once and wait for the response to all of them. Thus greater control is needed.
Imagine a some sort of server with objects like this (defined in a public assembly, let's say for server plugins):
Now for some sort of remote connection utility, a mirror of this interface can be used (there are other options, but this one is the easiest to use):
(Let's pretend for a moment that
async
properties exist, but this doesn't depend on their existence anyway.) The implementation ofIAsyncPlayer
can be coupled withIPlayer
at runtime to create a possible implementation, and all works well, but there are obvious issues:IAsyncPlayer
is fixed; changingIPlayer
requires chain changes, which could be a nuisance (although should be fixable with a source generator). Still such a type exists as a new type technically, so if multiple libraries intend to use the sameIAsyncPlayer
, they must reference a shared assembly (which in turn must be updated whenIPlayer
is changed, so we are again at the beginning).It would be really nice to have some sort of generic way of saying "it's like this type, but with these changes" where the original type and the changes themselves can be decoupled (or, more abstractly: "produce a type from these types"). That is, there should be a "type template"
Async<T>
that transformsAsync<IPlayer>
toIAsyncPlayer
. Moreover, if this template is defined in a common place,Async<IPlayer>
should mean exactly the same thing in any assembly that uses it (this demands some runtime support probably).It is non-trivial to design what this
Async<T>
should be, and probably even harder to find a syntax for it (it seems inevitable that it ends in something similar to a source generator anyway). But the point is that the resulting type should be interoperable with uses of such a type in other assemblies. Whether it would mean for every assembly to generate its own copy of the type and merging them all at runtime, or just somehow referencing them from code, that's up to the implementation.I can already imagine other such transformer types:
Interface<T>
. Simply takes the public non-static API of a type and forms an interface from it. While you cannot useDispatchProxy<Stream>
, at least you can now useDispatchProxy<Interface<Stream>>
if you promise to useInterface<Stream>
from now on. There should also beWrapped<T>
struct that takes an instance ofT
and implementsInterface<T>
via the instance (or maybe the runtime will autoimplementInterface<T>
for everyT
itself). Something to do with shapes seems also possible here.Interface<T>
should also derive from actual interfaces ofT
, andWrapped<T>
should implement them.StaticInterface<T>
. Like before, but take static members instead. UseStaticInterface<Console>
to control consoles of newly spawned .NET processes. It seems reasonable thatStaticClass<Console>
/StaticWrapped<Console>
should be a singleton implementingStaticInterface<Console>
then.ConfigureAwaitFalse<T>
. Exactly likeWrapped<T>
, but all calls are followed byConfigureAwait(false)
wherever possible.Boxed<T>
. A class implementingInterface<T>
of a value typeT
. Not needed if value types start implementingInterface<T>
of them, in which case there would already be an existing boxed representation of every value type.Memory<T>
. Something more abstract that logs all operations performed on it and can perform them on other instances ofT
when triggered.Snapshot<T>
. Similar toMemory<T>
, but only reflects the properties ofT
as a record type consisting of those properties.Snapshot<IPlayer>
could storeName
,Color
, andPing
consistently from one point of time.Synchronized<T>
. LikeWrapped<T>
, but every method call locks the instance ofT
. Attributes could be used to control this.One last example – consider you have created a drawing method that uses the GDI+
Graphics
class:This uses the
Graphics
class quite heavily to draw some very nice and complex things, but you realize that in the future, someone might want to use it with something else (that uses SVG for example). Let's say the use ofGraphics
in the method really warrants you saying "I need something that promises to behave just likeGraphics
". If you are on .NET Framework, you don't actually have to do anything, sinceGraphics
implementsMarshalByRefObject
and thus you can use aRealProxy
if you want to use your own class. But if you are on .NET Core (until proxying gets implemented again), you can now use:Now using it with a normal
Graphics
instance is easy – just useWrapped<Graphics>
and you have got a type that implementsInterface<Graphics>
, passes all operations to the originalGraphics
instance, and thanks to generics and inlining has no additional costs. All this additional versatility from a change to just one line of code. And this is still not duck-typing, since any new type used here must still implementInterface<Graphics>
and thus promise: "Yeah, I really work likeGraphics
."To reiterate the differences between this and source generators:
Async<Array>
used in assembly A must be identical toAsync<Array>
used in assembly B. This must hold both at compile time and at runtime.T
must depend solely on the current definition ofT
. If it is implemented with a type in the same assembly, other assemblies cannot simply use that one instead of creating their own, since the definition ofT
could have changed.As for the actual implementation of this, I can think of something along the lines of this:
TypeBuilder
in case of CIL).internal
.Beta Was this translation helpful? Give feedback.
All reactions