[Discussion]: Extensions #8696
Replies: 455 comments 680 replies
-
|
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.
-
|
It seems the C# Language Design Team are in dire need for a nice, intuitive syntax wich also supports all major cases while keeping binary compatibility with previous extension methods. So I went back and reread the comments from this discussion which have formed the state of the current language proposal as well as user-submitted ideas and concerns to address as many issues as possible. Here is my take of a possible syntax which could solve most goals: This is the very first example for Extension Members from the specs, rewritten in my proposed way: public extension<T> E for (T[] ts)
{
public bool M1(T t) => ts.Contains(t);
}Note that this isn't just a syntactical sugar over the current proposal, because this would generate a static class with a generic type parameter: [GeneratedCode]
public static class E<T>
{
public static bool M1(T[] ts, T t) => ts.Contains(t);
}instead of [GeneratedCode]
public static class E
{
public static bool M1<T>(T[] ts, T t) => ts.Contains(t);
}In this way we could preserve the arity of the generic arguments for the method, a major issue raised here: #8696 (comment) Furthermore, theoretically you would be able to omit the name of the extension and just rely on what the compiler can come up with: public extension<T> for (T[] ts)
{
public bool M1(T t) => ts.Contains(t);
}Which would be very similar to classless "extension groups" (a suggestion from Mads Torgersen's presentation). If needed, you could also alternatively write extension members individually in normal static class: public static class Enumerable
{
// Extension method with generic arity of 1
public extension<TSource> IEnumerable<TSource> Where(Func<TSource, bool> predicate)
for (IEnumerable<TSource> source)
{
...
}
// Extension method with generic arity of 2
public extension<TSource, TResult> IEnumerable<TResult> Select(Func<TSource, int, TResult> selector)
for (IEnumerable<TSource> source)
{
...
}
// Extension method with generic contsraint
public extension<TSource> IEnumerable<TSource> Average()
for (IEnumerable<TSource> source)
where TSource : INumberBase<TSource>
{
...
}
// Extension property
public extension<TSource> bool IsEmpty for (IReadOnlyCollection<TSource> source) => source.Count == 0;
// Extension operator
public extension<TSource> static IEnumerable<TSource> operator +(IEnumerable<TSource> first, IEnumerable<TSource> second)
for IEnumerable<TSource> // Note the lack of parentheses as they are not needed here
{
return source.Concat(other);
}
// Classic extension method
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
// Non-extension member
public static IEnumerable<int> Range(int start, int count) { ... }
}In this way you can get binary compatibility with the old extension methods, but you would also lose those groupings, though. However, you can always add support for that syntax later for those who are interested in such "sugar". Here is the public extension<TSource> Enumerable for (IEnumerable<TSource> source)
{
public IEnumerable<TSource> Where(Func<TSource, bool> predicate)
{
...
}
public IEnumerable<TResult> Select<TResult>(Func<TSource, int, TResult> selector)
{
...
}
public IEnumerable<TResult> OfType<TResult>()
where TResult : TSource
{
...
}
}Note that this extension can't include the IEnumerable<Animal> animals = [dog, cat, goldfish];
IEnumerable<Mammal> mammals = animals.OfType<Mammal>();because it would translate to IEnumerable<Mammal> mammals = Enumerable<Animal>.OfType<Mammal>(animals);And finally, here is an example for static only members: public extension MathExtensions for Math // Note the lack of parentheses as they are not needed here
{
public const double GoldenRatio = 1.61803398875;
public static BigInteger Pow(BigInteger value, int exponent)
{
return BigInteger.Pow(value, exponent);
}
public static T Pow<T>(T value, T exponent)
where T : IPowerFunctions<T>
{
return T.Pow(value, exponent);
}
} |
Beta Was this translation helpful? Give feedback.
-
|
Could we have 2-phase lookup by introducing discards for generic arguments that could use the old 1-phase lookup? extension(IEnumerable<_>)
{
// 1-phase lookup, for compat, generic argument in the method declaration
public static IEnumerable<T> M<T>(out T t) => ...;
}
extension<T>(IEnumerable<T>)
{
// 2-phase lookup, generic argument in the extension declaration
public static IEnumerable<T> M(out T t) => ...;
}Originally posted in dotnet/roslyn#78415 (comment). |
Beta Was this translation helpful? Give feedback.
-
|
I just ran into https://github.com/dotnet/roslyn/issues/78753 when playing with the preview, and I find this behavior unintuitive. Intuitively, the extension method + extension property feels more like extension method + instance property than like instance method + instance property. Some part of this likely comes from the error message seeming to indicate that something wasn't found at all, when in fact the error is an ambiguous result. This limitation does also block a migration path for some code (which I, at least, see somewhat frequently). Previously, if you wanted a property-like thing as an extension, you'd write something like this (using the case I ran into as an example): public static bool IsByRefLike(this Type type) => ...
// called like
type.IsByRefLike()This has 2 downsides:
The ideal upgrade path for this is to keep (but hide) the existing extension method, while adding an extension property of the same name. This then allows the natural code to work, and gives access to pattern matching and such. Per @jcouv's response, this is not allowed under current rules, which is a shame. It would be very nice if there were some resolution. I think the natural one is to allow resolution here, but I would accept even a magic "don't consider this method during overload resolution" attribute (or some other similar cudgel). |
Beta Was this translation helpful? Give feedback.
-
|
I updated Visual Studio to Version 17.14.2 this week and since then, following code leads to a compile error: [GeneratedCode("xsd", "4.7.3081.0")]
[Serializable]
[DebuggerStepThrough]
[DesignerCategory("code")]
[XmlType(AnonymousType = true, Namespace = "http://www.ech.ch/xmlns/eCH-0020/3")]
[XmlRoot(Namespace = "http://www.ech.ch/xmlns/eCH-0020/3", IsNullable = false)]
public class extension
{
private XmlElement[] anyField;
/// <remarks/>
[XmlAnyElement]
public XmlElement[] Any
{
get
{
return anyField;
}
set
{
anyField = value;
}
}
}
[GeneratedCode("xsd", "4.7.3081.0")]
[Serializable]
[DebuggerStepThrough]
[DesignerCategory("code")]
[XmlType(Namespace = "http://www.ech.ch/xmlns/eCH-0020/3")]
public class eventChangeMatrimonialInheritanceArrangement
{
private extension extensionField;
public extension extension
{
get
{
return extensionField;
}
set
{
extensionField = value;
}
}
}The error is "error CS9281: Extension declarations may not have a name." at the Line "private extension extensionField;". I highly suspect, that a recent PR about "Extension Members" is the culprit. I was a bit stumped, because I never saw a hint in the release notes about "extension" suddenly being a keyword. It's easy to fix in my case by prefixing the reference to the extension class with an @ like this: public class eventChangeMatrimonialInheritanceArrangement
{
private @extension extensionField;
public @extension extension
{
get
{
return extensionField;
}
set
{
extensionField = value;
}
}
}Maybe this helps someone who stumbles into this issue, too. |
Beta Was this translation helpful? Give feedback.
-
|
The new extension syntax is more relaxed in naming restrictions than normal classes. class Greet
{
public void Hello() => Console.WriteLine("Hello");
public static void Hello() => Console.WriteLine("Hello");
// Error: Type 'Greet' already defines a member called 'Hello' with the same parameter types
}A similar restriction is enforced when creating an instance and static extension method: static class GreetExtensions
{
extension(string s)
{
public void Hello() => Console.WriteLine("Hello");
public static void Hello() => Console.WriteLine("Hello");
// Error: Type 'GreetExtensions' already defines a member called 'Hello' with the same parameter types
}
}However, If you put these extension methods in two separate classes, the compiler (VS 1714.2) has no issue with it: static class GreetExtensions1
{
extension(string s)
{
public void Hello() => Console.WriteLine($"Hello {s}"); // no error
}
}
static class GreetExtensions2
{
extension(string)
{
public static void Hello() => Console.WriteLine("Hello"); // no error
}
}That seems like an inconsistency. Is the original class restriction still needed. |
Beta Was this translation helpful? Give feedback.
-
|
With this feature I can finally write the public static class NullParseExtensions
{
extension<T>(T?) where T : struct, IParsable<T>
{
public static T? Parse(string? str, IFormatProvider? format = null) => string.IsNullOrEmpty(str) ? null : T.Parse(str, format);
}
}but, crushingly, it doesn't quite work: |
Beta Was this translation helpful? Give feedback.
-
|
Why not go with a less indented/verbose syntax? This is ugly IMO. |
Beta Was this translation helpful? Give feedback.
-
|
|
Beta Was this translation helpful? Give feedback.
-
|
Hi, I tried this new feature with .NET 10 preview 5 and it's great! However I got errors when adding an operator extension according to the proposal (operator section). class Something<T> where T : INumber<T>
{
}
static class SomethingExtension
{
extension<T>(Something<T>) where T : INumber<T>
{
public static Something<T> operator +(Something<T> left, Something<T> right)
{
return new Something<T>();
}
}
}The errors are: Am I doing something wrong, or this feature has not been implemented? |
Beta Was this translation helpful? Give feedback.
-
|
Maybe time to get these
You hit these immediately when trying out the new extensions feature, but surprisingly few seem to complain. Code analysis, a rarely used feature? |
Beta Was this translation helpful? Give feedback.
-
|
Is it a "known issue" with new extension members and I am trying to convert the following method into an extension member: public static class MyExtensions
{
public static string GetRequiredToString<T>(this T? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) {
ArgumentNullException.ThrowIfNull(value, paramName);
return value.ToString() ?? "";
}
// Re-writing above as:
extension<T>(T? value) {
public string GetNewRequiredToString([CallerArgumentExpression(nameof(value))] string? paramName = null) {
ArgumentNullException.ThrowIfNull(value, paramName);
return value.ToString() ?? "";
}
}
}and I got the "warning CS8963: The CallerArgumentExpressionAttribute applied to parameter 'paramName' will have no effect. It is applied with an invalid parameter name.". I understand the issue and was not able to find a discussion about that. |
Beta Was this translation helpful? Give feedback.
-
|
There seems to be no way to document the extension block itself. Also generic parameters can not be documented. Was this completely forgotten? Or is VS2026 tooling not up to the task? So how is this supposed to be done? This looks ugly and contains duplicate stuff. While tooltips are showing, automatic tooling by entering /// does not work: /// <summary>
/// Extensions for the <see cref="IQueryable{T}"/> interface.
/// </summary>
public static class IQueryableExtensions
{
/// <summary>
/// Extensions for the <see cref="IQueryable{T}"/> interface.
/// </summary>
/// <param name="source">An <see cref="IQueryable{T}"/> to create an <see cref="ImmutableList{T}"/> from.</param>
/// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam>
extension<TSource>(IQueryable<TSource> source)
{
/// <summary>
/// Creates an <see cref="ImmutableList{T}"/> from an <see cref="IQueryable{T}"/>.
/// </summary>
/// <returns>An <see cref="ImmutableList{T}" /> that contains elements from the input sequence.</returns>
public ImmutableList<TSource> ToMyImmutableList() => source.ToImmutableList();
}
} |
Beta Was this translation helpful? Give feedback.
-
|
Why do extension members still require the LangVersion to be set to preview for it to work? I'm trying to define an extension property in a .NET 10 targeted project, but it won't compile unless the LangVersion is set to preview. |
Beta Was this translation helpful? Give feedback.
-
|
Are |
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.
-
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