[Discussion]: Extensions #8696
Replies: 377 comments
-
I prefer using 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 { }
|
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
@hez2010 public extension Foo for IA : IB, IC { } |
Beta Was this translation helpful? Give feedback.
-
Who gave you an early preview of my notes? They're up now, discussion at #5500. |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
@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 |
Beta Was this translation helpful? Give feedback.
-
I'm not sure if I should re-post my comments from the discussion here?
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. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
@TahirAhmadov That works, more or less, for the specific example I gave, but what if |
Beta Was this translation helpful? Give feedback.
-
If it's not the main motivation, surely it shouldn't be the one discussed in the OP, should it? |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
The |
Beta Was this translation helpful? Give feedback.
-
Back with .NET Framework, I've often ran into situations where i wanted a The only thing I don't quite get is why we need two keywords here, |
Beta Was this translation helpful? Give feedback.
-
That's the thing, it would be very interesting to see an example which would demonstrate how |
Beta Was this translation helpful? Give feedback.
-
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! |
Beta Was this translation helpful? Give feedback.
-
I'm begging you to ditch the syntactic abomination that is the most recent extensions proposal — this is the ugliest syntax I could possibly imagine for this feature: public static class Extensions
{
extension<T>
{
public bool (List<T>).IsEmpty => this.Count == 0;
}
} What the heck is the point of the enclosing class?! Why introduce a million nesting levels?!?! This is aesthetically horrendous. Compare it with this: public extension Extensions<T>(List<T>)
{
public bool IsEmpty => this.Count == 0;
} Is there any sane human being that could look at these examples and be like "yep the first one looks so much more intuitive"? Update: Please, for the love of god, consider this proposal instead. |
Beta Was this translation helpful? Give feedback.
-
I suggest you check out the discussion here: The design space for extension It lays out the problems with the extension type syntax, the concerns that the language team are trying to address and a variety of different forms of syntax that are an attempt to address it. Many of these proposals are explorations in the design to see how the concerns can be addressed, and the reason for wanting to enclose extensions within a separate class is explicitly discussed. |
Beta Was this translation helpful? Give feedback.
-
Before I read the latest proposal, I had been thinking about the syntax options for extensions and this was what I suspected the outcome would be. To me it makes sense. Consider:
On top of that, you should pick a syntax that might work everywhere, even top-level or in method bodies. This might not be the prettiest syntax, but it is the most general syntax that simply works everywhere that makes sense. You would eventually converge on this type of syntax, be it That being said, I 100% expect the development to go with shortcuts that make using this syntax easier. Compare the I am sure this will be the case, if not in the same version of C# that gets the general syntax, then in the one after that. To be fair though, I do feel unsure about the parentheses in public static class Extensions
{
public extension<T>(List<T>) bool IsEmpty => this.Count == 0;
} Having a single unified syntax that works for members, for classes, and for scopes would be a major win. |
Beta Was this translation helpful? Give feedback.
-
I wonder why not keeping a syntax similar to the one currently used by extension methods: public static class Extension {
extensions<T>(this List<T> source) {
bool IsEmpty => source.Count == 0;
}
} I like |
Beta Was this translation helpful? Give feedback.
-
@HaloFour The reasons provided for the current abomination would be rectified by this new proposal. |
Beta Was this translation helpful? Give feedback.
-
Add some generics. |
Beta Was this translation helpful? Give feedback.
-
public extension<TSource> Enumerable(IEnumerable<TSource> source)
{
public bool All(Func<TSource, bool> predicate);
} |
Beta Was this translation helpful? Give feedback.
-
That's a possibility. I have suggested that whatever syntax could be applied in different scopes, from a single member, to groups of members, to all of the members declared within a type, etc. A separate clause also has the advantage of being declared within another member, and I, personally, think that extension implementation could be compelling for allowing hyper-localized extensions that could capture state. Only supporting it at the type level comes with the issue of having to declare potentially many types to capture the design space we already have. Is that better? That's extremely subjective. I don't find a level of indentation to be onerous, the IDE is doing that for me anyway. |
Beta Was this translation helpful? Give feedback.
-
This appears to just be bike shedding on collapsing syntactic forms. Something virtually every design member I've heard from seems fine with. As I've mentioned many times now. We are not doing the bikeshedding portion currently. We are deciding on core capabilities and semantics. Syntactic pleasantries come last. We have to start though with a STRAWMAN syntax that clearly delineates all the capabilities we want and answers all the questions on every complex case without confusion (like how do different generics merge) |
Beta Was this translation helpful? Give feedback.
-
And as I've mentioned, my feeling is that virtually all of the ldm is on board with that. But it's not an area of focus now because it doesn't change anything fundamentally. That's just syntactic sugar for later. We need to be dealing with the vet hard problems around semantics, compat, etc. first. Also, personally, I very much want this. For example, I definitely have extensions and non-extensions side by side today. Being forced to group these separately is a downside for me. So while I am totally fine with higher level grouping constructs, I still want to be able to apply things at an individual (or small group) level. |
Beta Was this translation helpful? Give feedback.
-
I'm confused by something in the recent Unified Extensions proposal: (#8665) Given this sample: public static class NullableExtensions
{
extension([NotNullWhen(false)] string? text)
{
public bool IsNullOrEmpty => text is null or [];
}
} It seems odd to me to have nullability attributes on the reciever rather than on the method. I would have expected something more like: public static class NullableExtensions
{
extension(string? text)
{
[this: NotNullWhen(false)]
public bool IsNullOrEmpty => text is null or [];
}
} Otherwise, are there additional restrictions on the methods inside the extension scope? What would the following mean, or would it become a warning/error? public static class NullableExtensions
{
extension([NotNullWhen(false)] string? text)
{
public object Foo => throw null!; // doesn't return a bool
public void Foo => throw null!; // doesn't return anything
}
} |
Beta Was this translation helpful? Give feedback.
-
This exactly matches existing extension methods. The return value tells you the state of the receiver value on exit. |
Beta Was this translation helpful? Give feedback.
-
Yes, but on current extension methods you would put it on a method that returns bool, not on a scope containing methods which may or may not return bool. Though with that said, there is nothing stopping you from writing the following at the moment: using System.Diagnostics.CodeAnalysis;
public static class C
{
public static void M([NotNullWhen(false)] this object? x) {
throw null!;
}
} But it does also seem odd to me to force users of the new extension syntax to group methods by their nullability state, rather than any other logical grouping. |
Beta Was this translation helpful? Give feedback.
-
Or something like this public static class NullableExtensions
{
extension([NotNullWhen(false)] string? text)
{
public bool IsNull => return this == null;
public bool IsNotNull => return this != null;
}
} Now what I think something like this is much better (if there actually was public static class NullableExtensions
{
extension(string? text)
{
[this: NotNullWhen(false)]
public bool IsNull => return this == null;
[this: NotNullWhen(true)]
public bool IsNotNull => return this != null;
}
} |
Beta Was this translation helpful? Give feedback.
-
Thinking about the top-level syntax, we can use: public extension<T>(IEnumerable<T> source) as Enumerable
{
// ....
} advantages:
examples: public partial extension<T>(IEnumerable<T> source) as Enumerable where T : IFoo
{
// ...
}
public partial extension<T>(IEnumerable<T> source) as Enumerable where T : IBar
{
// ...
}
public partial extension(IEnumerable<int> source) as Enumerable
{
// ...
}
public partial extension(IEnumerable<long> source) as Enumerable
{
// ...
}
public partial extension(string? source) as Enumerable
{
// ...
} instead of public static class Enumerable
{
extension<T>(IEnumerable<T> source) where T : IFoo { ... }
extension<T>(IEnumerable<T> source) where T : IBar { ... }
extension(IEnumerable<int> source) { ... }
extension(IEnumerable<long> source) { ... }
extension(string? source) { ... }
} |
Beta Was this translation helpful? Give feedback.
-
Discussed in #5496
Originally posted by MadsTorgersen November 30, 2021
https://github.com/dotnet/csharplang/blob/main/proposals/extensions.md
Beta Was this translation helpful? Give feedback.
All reactions