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]: Extensions #5497

Open
1 of 4 tasks
333fred opened this issue Dec 2, 2021 Discussed in #5496 · 180 comments
Open
1 of 4 tasks

[Proposal]: Extensions #5497

333fred opened this issue Dec 2, 2021 Discussed in #5496 · 180 comments
Assignees
Milestone

Comments

@333fred
Copy link
Member

333fred commented Dec 2, 2021

Discussed in #5496

Originally posted by MadsTorgersen November 30, 2021

Extensions

LDM Meetings

https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-12-01.md#roles-and-extensions
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-31.md#roles
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-26.md#roles--extensions
https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-22.md#extensions
https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-12-11.md#extensions
https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-28.md#extensions

@hez2010
Copy link

hez2010 commented Dec 2, 2021

I prefer using of or something else to distinguish the type being extended and interfaces:

public extension Foo of int : IA, IB, IC, ...
{
    ...
}

Otherwise it will be too confusing if you are extending an interface:

public extension Foo : IA, IB, IC { }

vs

public extension Foo of IA : IB, IC { }

of can be relatively safely made as a keyword since it's neither a verb nor a noun, so almost nobody would choose of as an identifier.

@HaloFour
Copy link
Contributor

HaloFour commented Dec 2, 2021

I'm curious as to how the team weighs the relative benefits between "roles" and "extension implementation". It feels that without some additional effort in the runtime the two are somewhat incompatible with each other, so if those differences can't be reconciled which of the features might the team lean towards?

Personally, I find extension implementation much more exciting than roles, but that's just my opinion.

@FaustVX
Copy link

FaustVX commented Dec 2, 2021

@hez2010
Maybe, instead of of, for would be a better name for the keyword as it's already a keyword.

public extension Foo for IA : IB, IC { }

@333fred
Copy link
Member Author

333fred commented Dec 2, 2021

I'm curious as to how the team weighs the relative benefits between "roles" and "extension implementation". It feels that without some additional effort in the runtime the two are somewhat incompatible with each other, so if those differences can't be reconciled which of the features might the team lean towards?

Who gave you an early preview of my notes? They're up now, discussion at #5500.

@sab39
Copy link

sab39 commented Dec 3, 2021

Here's a scenario that will be great fun to try to accommodate in the design:

interface IFoo { }
interface IBar { }
class Thing { }
public extension FooThing for Thing : IFoo { }
public extension BarThing for Thing : IBar { }
void Frob<T>(T t) where T : IFoo, IBar { }

Frob(new Thing());

On an unrelated bikeshedding note, what about using the existing reserved keywords explicit and implicit as modifiers, rather than treating roles and extensions as completely separate things? An extension is, more or less, just a role that gets applied implicitly based on type rather than needing to be explicitly named in the declaration. Using implicit role as the syntax would spell that correspondence out more (no pun intended) explicitly?

@CyrusNajmabadi
Copy link
Member

@sab39 Given, as you've mentioned, how similar these two concepts are. I too am looking for a good syntactic way to convey that similarity, with a clear way to do indicate in which way they differ. Thanks for the explicit/implicit idea, definitely something we'll consider!

@TahirAhmadov
Copy link

I'm not sure if I should re-post my comments from the discussion here?
In short, I think the extensions and especially interface "adapters" make sense, but roles don't. The main motivation example - DataObject - is an anti-pattern, IMO; it runs into expensive runtime changes; and causes confusion - now you have to keep in mind when looking at an identifier in a "type receiving context" if it's a type or a role.

Here's a scenario that will be great fun to try to accommodate in the design:

interface IFoo { }
interface IBar { }
class Thing { }
public extension FooThing for Thing : IFoo { }
public extension BarThing for Thing : IBar { }
void Frob<T>(T t) where T : IFoo, IBar { }

Frob(new Thing());

This is complicated, but doable using current constraints of the framework. An anonymous type can be generated:

class <mangled>Thing_IFoo_IBar : IFoo, IBar
{
  internal <mangled>Thing_IFoo_IBar(Thing thing) { this._thing = thing; }
  readonly Thing _thing;

  void IFoo.Foo() { ... } // these member(s) are copied from, or call into, FooThing
  void IBar.Bar() { ... } // these member(s) are copied from, or call into, BarThing
}
Frob(new <mangled>Thing_IFoo_IBar(new Thing()));

The same can be done for generic types, etc. Yes, it's complicated, but unlike roles, it's very possible.

@CyrusNajmabadi
Copy link
Member

The main motivation example

This was just one example. It's not the main motivation. We discussed in the LDM that there were definitely plenty of scenarios where you'd still want adapters in a strongly typed way that would be sensible.

@sab39
Copy link

sab39 commented Dec 3, 2021

@TahirAhmadov That works, more or less, for the specific example I gave, but what if Frob<T> had other constraints like where T : class or where T : struct or where T : new() or where T : SomeBaseClass? What about if it were Frob<T, T2> where T2 : T? In general it's not actually possible to generate an anonymous type that can meet all possible constraints that T would meet and also implement IFoo and IBar. This suggests to me that it's not possible to fully support this scenario without runtime assistance.

@TahirAhmadov
Copy link

The main motivation example

This was just one example. It's not the main motivation. We discussed in the LDM that there were definitely plenty of scenarios where you'd still want adapters in a strongly typed way that would be sensible.

If it's not the main motivation, surely it shouldn't be the one discussed in the OP, should it?

@CyrusNajmabadi
Copy link
Member

The OP is simply showing a demonstration. This is a broad topic and we need to spend a ton more time on it prior to even getting close to a place where we could write something up that was fully fleshed out and chock full of examples and whatnot.

@TahirAhmadov
Copy link

@TahirAhmadov That works, more or less, for the specific example I gave, but what if Frob<T> had other constraints like where T : class or where T : struct or where T : new() or where T : SomeBaseClass? What about if it were Frob<T, T2> where T2 : T? In general it's not actually possible to generate an anonymous type that can meet all possible constraints that T would meet and also implement IFoo and IBar. This suggests to me that it's not possible to fully support this scenario without runtime assistance.

The where T : class constraint simply rejects Thing extensions if Thing doesn't satisfy the constraints. The where T : new() rejects all extensions outright. where T : SomeBaseClass also rejects extensions of Thing because it's a different type. Frob<T, T2> where T2 : T is completely irrelevant.

@BhaaLseN
Copy link

BhaaLseN commented Dec 3, 2021

Back with .NET Framework, I've often ran into situations where i wanted a Math.Clamp<T>(T value, T min, T max), Math.Max(TimeSpan val1, TimeSpan val2) or Path.GetRelativePath(...) for discoverability; but there was no way for me to get this done. Same with string.Contains(string, StringComparison) etc. except as instance extension (which is somewhat taken care of by extension methods though.)
Nowadays most are in there since either .NET Core or .NET 5/6, but it would feel more natural to simply extend the existing classes (with a very high-up namespace inside the project, so its most likely in scope all the time; or with a global using for example) when another one of those situations comes up. At least compare to MathEx, MathUtilities, PathHelpers etc. which often aren't as obvious.

The only thing I don't quite get is why we need two keywords here, role and extension. They mean different things if you talk about them, sure, but does this actually matter once you write the code? I'd assume they'll be lowered to virtually the same thing during compilation after all, and I can practically hear the "what's the difference" question coming when I present this to my team during a knowledge transfer meeting.

@TahirAhmadov
Copy link

TahirAhmadov commented Dec 3, 2021

The OP is simply showing a demonstration. This is a broad topic and we need to spend a ton more time on it prior to even getting close to a place where we could write something up that was fully fleshed out and chock full of examples and whatnot.

That's the thing, it would be very interesting to see an example which would demonstrate how roles make something worthwhile possible or significantly easier, before effort is spent on prototypes, implementation planning, etc.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Dec 3, 2021

That's fine. It's something we're working on at this moment '-). The point was raised and was something we intend to get to and write more on. I def don't want us to get the impression that it's just for that. Thanks!

@orthoxerox
Copy link

Roles feel like they need a validator method, something that is invoked to by the "implicit conversion" to ensure that the underlying object can fill in that role. I'm not even sure the conversion should be implicit. I'm sure it will be annoying to do stuff like public Customer Customer => (Customer)this["Customer"]; over and over again, but I also want to be able to say if (payload is Order order) { ....

@sab39
Copy link

sab39 commented Dec 3, 2021

Hmm, that almost makes it sound like you want Extension DUs...

@orthoxerox
Copy link

I don't want them to be a DU per se, it's more similar to getting an object from some API and casting it to the expected type. Right now the roles work more like dynamic instead.

@vladd
Copy link

vladd commented Dec 3, 2021

@orthoxerox F# has a feature Partial Active Patterns which looks somewhat like your idea.

@hez2010
Copy link

hez2010 commented Dec 4, 2021

This is complicated, but doable using current constraints of the framework. An anonymous type can be generated:

class <mangled>Thing_IFoo_IBar : IFoo, IBar
{
  internal <mangled>Thing_IFoo_IBar(Thing thing) { this._thing = thing; }
  readonly Thing _thing;

  void IFoo.Foo() { ... } // these member(s) are copied from, or call into, FooThing
  void IBar.Bar() { ... } // these member(s) are copied from, or call into, BarThing
}
Frob(new <mangled>Thing_IFoo_IBar(new Thing()));

The same can be done for generic types, etc. Yes, it's complicated, but unlike roles, it's very possible.

C# isn't the only language on CoreCLR, without runtime support how would you expect roles to be defined and used in other languages? Other languages don't recognize the mangled anonymous class.

@TahirAhmadov
Copy link

C# isn't the only language on CoreCLR, without runtime support how would you expect roles to be defined and used in other languages? Other languages don't recognize the mangled anonymous class.

The pseudocode I wrote was specifically for extensions, not roles.
In any case, though, the mangled anonymous type is generated at the call site, not at the extension declaration site. Specifically because there can be multiple permutations of extensions (or roles - ignoring the fact that I don't like the idea of roles/shapes), these machinations have to be performed when all the information is available: what interfaces are "adapted", etc.
Also, regardless of the language, the extension will have to be added somehow to the metadata; the easiest way would be using a class with some special attributes. The other languages can decide whether to implement this feature - in which case they can interpret these attributes like C# does; otherwise, it becomes a class, probably a static one, which they can use in an "old school way". The same is true for existing extension methods.
Further, even when it's a simple scenario, for interface "adaptation" to work, the easiest way is again, an anonymous type:

class Thing { }
interface IFoo { void Foo(); }
extension FooThing: Thing, IFoo { void Foo() { ... } }
void Frob(IFoo foo) { }
// this line:
Frob(new Thing());
// is compiled to this:
class <mangled>Thing_IFoo : IFoo
{
  internal <mangled>Thing_IFoo(Thing thing) { this._thing = thing; }
  readonly Thing _thing;

  void IFoo.Foo() { ... } // these member(s) are copied from, or call into, FooThing
}
Frob(new <mangled>Thing_IFoo(new Thing()));

@Thaina
Copy link

Thaina commented Dec 4, 2021

I also want to voice that I wish there would be some keyword being reused instead of casting new keyword role and extension. Or at least create only one and use implicit/explicit as above

Or implicit class possible?

Aside from that I have nothing against, and fully support this issue

@hez2010
Copy link

hez2010 commented Dec 4, 2021

instead of casting new keyword role and extension. Or at least create only one and use implicit/explicit as above

Or implicit class possible?

Keywords can be introduced as contextual keywords so it can be made not to introduce breaking changes.

@Thaina
Copy link

Thaina commented Dec 4, 2021

@hez2010 I know there is no breaking change but it still should be the last option to introduce any new keyword. If there would be any possible for composite or reuse then we should

@333fred
Copy link
Member Author

333fred commented Dec 4, 2021

I found the idea of implicit and explicit very interesting and forwarded your comment to our working group @sab39, thanks for the suggestion!

@iam3yal
Copy link
Contributor

iam3yal commented Dec 4, 2021

I don't get why roles need an implicit and explicit modifiers, can't they be applied based on the context? what does it mean to have these modifiers? why treat them more or less the same and not exactly the same where the only difference is context? I get the you're trying out different ideas but merge these concepts needs to be core principle the way I think about is similar to aggregation vs composition where aggregation is an extension of existing type and composition is a wrapper the only difference is what they user want them to be based on context and not how they were constructed I don't think we want to end up with a situation where "I can do this when it's explicit but not when it's implicit or vice-versa" but maybe I'm misunderstanding why we need to have different rules for these two concepts, it's not clear whether they are similar or identical features yet but based on the OP I think they are either identical or similar to the point where it can be confusing to grasp why we are speaking about two different concepts.

I'll just copy/paste my comment from the other post so something like this:

// Customer.cs
namespace Data;

public extension Customer : DataObject // Wrapper type
{
    public string Name => this["Name"].AsString();
    public string Address => this["Address"].AsString();
    public IEnumerable<Order> Orders => this["Orders"].AsEnumerable();
}

// JsonDataObject.cs
namespace Data;

using JsonLibrary;

public extension JsonDataObject : DataObject // Extension type
{
    public string ToJson() { … this … }
    public static T FromJson<T>(string json) {}
}

// Program.cs / Main method
using Data;
using Data.JsonDataObject; // Importing a specific extension type
using Data.*; // Importing all extensions types in the namespace Data

var customer = customer.FromJson<Customer>(args[0]);
WriteLine(customer.ToJson());

@Reinms
Copy link

Reinms commented Dec 4, 2021

Would this be allowed under roles?

role Foo<T> : T
    where T : ISomeInterface
{
}

or would we be forced to directly extend the interface and bring in boxing conversions all over the place as we implicitly cast back and forth in a generic function?

@TahirAhmadov
Copy link

TahirAhmadov commented Dec 4, 2021

Thinking about it, I imagine this happening:

class Thing { }
interface IFoo { void Foo(); }
// the following line
public extension FooThing: Thing, IFoo { void Foo() { ... } }
// is compiled to:
// these attributes are once per assembly, similar to NRT attributes
class ExtensionTypeAttribute { public ExtensionTypeAttribute(params Type[] types) { ... } ... }
class ExtensionInstanceMemberAttribute {  }
class ExtensionStaticMemberAttribute {  }
// the actual extension becomes:
[ExtensionType(typeof(Thing), typeof(IFoo))]
public static class FooThing
{
  [ExtensionInstanceMember]
  public static void Foo(Thing @this) { ... }
}

void Frob(IFoo foo) { }

// this line:
Frob(new Thing());
// is compiled to this:
class <mangled>Thing_IFoo : IFoo
{
  internal <mangled>Thing_IFoo(Thing thing) { this._thing = thing; }
  readonly Thing _thing;

  void IFoo.Foo() { FooThing.Foo(this._thing); }
}
Frob(new <mangled>Thing_IFoo(new Thing()));

@michael-hawker
Copy link

Thanks @HaloFour for splitting off and summarizing all the detailed discussion here. Makes sense to have as a spin-off discussion and address even potentially independently from this feature. Do like the protected modifier to existing extension syntax as an option, seems straightforward from an understanding/usage standpoint.

@HaloFour
Copy link
Contributor

HaloFour commented Mar 3, 2023

@BlinD-HuNTeR

That seems like a pretty big hack vs. a scenario fully intended to be supported by both the language and runtime for general use. But if such a language feature can build on top of that, all the better.

@hamarb123
Copy link

hamarb123 commented Mar 3, 2023

Note that mono only added support for IgnoresAccessChecksToAttribute in 2021 apparently (source), if we used this anywhere, we may want to use a feature flag to determine runtime support.

@BlinD-HuNTeR
Copy link

BlinD-HuNTeR commented Mar 8, 2023

Note that mono only added support for IgnoresAccessChecksToAttribute in 2021 apparently (source), if we used this anywhere, we may want to use a feature flag to determine runtime support.

Actually, if what is being discussed is just protected access, then it's possible to implement that even without this attribute, therefore supporting all existing runtimes. I personally use this trick in some helper methods:

class ThirdPartyClass
{
	protected void Method1() { }
}

class Accessor : ThirdPartyClass
{
	public void Method1Accessor() => Method1();
}

static class Extensions
{
	public static void Method1ButPublic(this ThirdPartyClass obj) => Unsafe.As<Accessor>(obj).Method1Accessor();
}

This trick can be used to freely invoke any protected member, even virtual ones, as long as the accessor is non-virtual. Should this feature be implemented on top of that, the compiler could easily cook the Accessor class behind the scenes and route the method call through it, eliminating the dependence on IgnoresAccessChecksToAttribute.

However, quoting my own post from last year, I hold the opinion that encapsulation breaking should not be built into the language. If the developer wants that, it's their responsibility to use the Unsafe trick or the attribute.

EDIT: Just realized that @HaloFour created a separate discussion for the theme of protected member access. Should I move/copy this post into that?

@HaloFour
Copy link
Contributor

HaloFour commented Mar 8, 2023

@BlinD-HuNTeR

Those are just tricks, though, and there's no guarantee that they will work on any given runtime. I don't think it's safe to rely on being able to use that in general purpose libraries, especially since they do break encapsulation.

I hold the opinion that encapsulation breaking should not be built into the language.

If the language were to consider something like this it should be on much tighter guardrails. The language can ensure, for example, that you can only call these members from within a protected context, so that you're not breaking encapsulation. Either way, there seems to be some desire for functionality like this, so it's worth the conversation. But I don't think it's necessarily tied to extensions, so the conversation should probably be on the other discussion.

@ds5678
Copy link

ds5678 commented Apr 22, 2023

Can extension types be nested?

extension Extension for UnderlyingType1
{
    public extension PublicNestedExtension for UnderlyingType2 { }
}
class Class
{
    private extension PrivateNestedExtension for UnderlyingType3 { }
}
class UnderlyingType1 { }
class UnderlyingType2 { }
class UnderlyingType3 { }

I didn't see any mention of this in the proposal.

@alfosua
Copy link

alfosua commented Apr 22, 2023

@ds5678, I'd say that such an extension would be only usable in that class scope.

@TahirAhmadov
Copy link

@alfosua For implicit extension, yes that makes sense, but what about explicit extension (previously known as role)? I don't see a reason why it shouldn't be allowed to "cast" to it.

implicit extension Extension for UnderlyingType1
{
    public explicit extension PublicNestedExtension for UnderlyingType2 { }
}
class UnderlyingType1 { }
class UnderlyingType2 { }
var x = new UnderlyingType2();
var y = x as Extension.PublicNestedExtension;

@KennethHoff
Copy link

KennethHoff commented Jul 16, 2023

I have not seen this mentioned/addressed, but I might've simply missed it. As I've understood it - and correct me if I'm wrong - implicit extensions are a superset of the "old" way. With this in mind:

When (if?) this launches, will there be a push to obsolete the old way? Obviously not on a language basis(backwards compatibility etc..), but in documentations and in the official libraries? Will there be a code fix to convert the old syntax to the new? Will there be analyzers (information-level probably) that "warns" you about using legacy syntax?

We - as a community or even individual teams - can of course create our own, but I'm wondering about what the team thinks on the matter.

@CyrusNajmabadi
Copy link
Member

When (if?) this launches, will there be a push to obsolete the old way?

Yes/no.

This is not really a superset. Today it is fine (and a totally reasonable pattern) to have a single static class with extensions on many disparate types.

In the case where you have a single static class just with extensions on a single type, we probably will have a fixer to suggest moving to the new form.

We would not deprecate it remove the existing form from the language.

@KennethHoff
Copy link

Today it is fine (and a totally reasonable pattern) to have a single static class with extensions on many disparate types.

That's something I didn't consider - good thing I'm not a language designer, eh? 😅

With that in mind, your plan seems to be inline with what I was hoping for! 👍

@dersia
Copy link

dersia commented Jul 25, 2023

Is this somehow available on sharplab? I tried to use the branch on sharplab, but unfortunatley I always get an exception

using System;
public class UnderlyingType {
    public required string Id { get; set; }
}

explicit extension Ext1 for UnderlyingType {
    public string Name { get; set; }
}
System.InvalidOperationException: Unexpected value 'Extension' of type 'Microsoft.CodeAnalysis.CSharp.DeclarationKind'
   at Microsoft.CodeAnalysis.CSharp.DeclarationTreeBuilder.CachesComputedMemberNames(SingleTypeDeclaration typeDeclaration) in /_/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs:line 119
   at Microsoft.CodeAnalysis.CSharp.SyntaxAndDeclarationManager.<ReplaceSyntaxTree>g__tryGetLastComputedMemberNames|13_0(SyntaxTree oldTree, Builder declMapBuilder, Builder lastComputedMemberNamesMap) in /_/src/Compilers/CSharp/Portable/Compilation/SyntaxAndDeclarationManager.cs:line 639
   at Microsoft.CodeAnalysis.CSharp.SyntaxAndDeclarationManager.ReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree) in /_/src/Compilers/CSharp/Portable/Compilation/SyntaxAndDeclarationManager.cs:line 501
   at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.ReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree) in /_/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs:line 1012
   at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.CommonReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree) in /_/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs:line 3732
   at Microsoft.CodeAnalysis.SolutionState.UpdateDocumentInCompilationAsync(Compilation compilation, DocumentState oldDocument, DocumentState newDocument, CancellationToken cancellationToken) in /_/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs:line 1387
   at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.BuildDeclarationCompilationFromInProgressAsync(InProgressState state, Compilation compilationWithoutGenerators, CancellationToken cancellationToken) in /_/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs:line 635
   at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.BuildFinalStateFromInProgressStateAsync(SolutionState solution, InProgressState state, Compilation inProgressCompilation, CancellationToken cancellationToken) in /_/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs:line 606
   at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.BuildCompilationInfoAsync(SolutionState solution, CancellationToken cancellationToken) in /_/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs:line 514
   at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.GetOrBuildCompilationInfoAsync(SolutionState solution, Boolean lockGate, CancellationToken cancellationToken) in /_/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs:line 464
   at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.GetCompilationSlowAsync(SolutionState solution, CancellationToken cancellationToken) in /_/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs:line 421
   at MirrorSharp.Internal.Roslyn.RoslynSession.GetDiagnosticsAsync(CancellationToken cancellationToken) in D:\a\SharpLab\SharpLab\!roslyn-branches\dotnet-features-roles\sharplab\source\#external\mirrorsharp\Common\Internal\Roslyn\RoslynSession.cs:line 102
   at MirrorSharp.Internal.Handlers.SlowUpdateHandler.ExecuteAsync(AsyncData data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) in D:\a\SharpLab\SharpLab\!roslyn-branches\dotnet-features-roles\sharplab\source\#external\mirrorsharp\Common\Internal\Handlers\SlowUpdateHandler.cs:line 40
   at MirrorSharp.Internal.Connection.ReceiveAndProcessInternalAsync(CancellationToken cancellationToken) in D:\a\SharpLab\SharpLab\!roslyn-branches\dotnet-features-roles\sharplab\source\#external\mirrorsharp\Common\Internal\Connection.cs:line 118
   at MirrorSharp.Internal.Connection.ReceiveAndProcessAsync(CancellationToken cancellationToken) in D:\a\SharpLab\SharpLab\!roslyn-branches\dotnet-features-roles\sharplab\source\#external\mirrorsharp\Common\Internal\Connection.cs:line 73

@333fred
Copy link
Member Author

333fred commented Jul 25, 2023

That is as far as has been implemented in the branch. Nothing further exists yet.

@KennethHoff
Copy link

KennethHoff commented Sep 22, 2023

One thing I've been wondering recently after reading it some time ago; Using explicit extensions - previously "roles" - as Typed values/Typed Ids.

Which parts of this will work as expected?

public explicit extension PersonId for Guid; // I'm assuming body-less will be possible.

public class Person 
{
	public PersonId Id { get; init; } // I'm assuming properties can be explicit extensions
}

var person1 = new Person
{
	Id = Guid.NewGuid(), // I'm assuming(and hoping..) this will be a compiler error.
}

var person2 = new Person
{
	Id = PersonId.NewGuid(), // I'm assuming I still have access to the underlying methods, but I'm also assuming it will still return a `Guid` Type, so we have to downcast it somehow.
}

var person3 = new Person
{
	Id = (PersonId)PersonId.NewGuid(), // I'm assuming we have to do this? We could probably just make a `New()` method on `PersonId` directly though instead of this dance.
}

// ...

public interface IPersonService
{
	public Person GetById(PersonId id);
	public Person GetByRawGuid(Guid id);
}

IPersonService personService = ...;
PersonId personId = ...;
Guid guid = ...;

var person4 = personService.GetById(personId); // I'm assuming this will work
var person5 = personService.GetById(guid); // I'm assuming(and hoping..) this will not work for the same reason as I cannot instantiate `person1`
var person6 = personService.GetByRawGuid(personId); // I'm assuming this will work as it gets upcasted to a Guid
var person7 = personService.GetByRawGuid(guid); // This obviously works.

@TahirAhmadov
Copy link

It would be good if a fixer was introduced to convert existing static classes with extension methods with an extension. It would be even better if said fixer was able to handle partial static class and change those partial extension.

@jantajov
Copy link

Any update for 2024?

@ds5678
Copy link

ds5678 commented Jan 28, 2024

Any update for 2024?

There is an upcoming LDM meeting which will discuss extensions.

@scarletquasar
Copy link

I can't understand why #92689 was closed

@juepiezhongren
Copy link

any plan for extension

@thomaslevesque
Copy link
Member

I can't understand why #92689 was closed

I assume you mean dotnet/runtime#92689. It was closed for the reasons cited in the comments, and has nothing to do with this issue.

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