[Discussion]: Extensions #8696
Replies: 424 comments 359 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.
-
Regarding the syntax, i think letting the user name the "source" is unnecessary and leads to inconsistent syntax. Since we already have a keyword that refers to the current instance, extension List<T>
{
public IsEmpty => this.Count == 0;
public static List<T> Create() => [];
} this would also make extensions naturally work as a top level declaration, if that were to be added in the future. to anyone seeing this in the future, i found LDM on using |
Beta Was this translation helpful? Give feedback.
-
I thought one of the goals was to be able to implement interfaces as extensions. Am I wrong? Will this feature be ever implemented? public class User
{
public string Username { get; set; }
public string Password { get; set; }
}
extension UserExtensions(User user) : IEquatable<User>
{
public bool Equals(User? other)
{
if (other is null) return false;
return user.Username == other.Username &&
user.Password == other.Password;
}
} |
Beta Was this translation helpful? Give feedback.
-
Another question just came to my mind regarding extensions and different overloads of the same method with different where conditions. Let's say we have an extension: public static class EqualExtensions {
extension<T>(T self) where T : INumber<T>
{
public bool IsEqualTo(T other) => self == other;
}
extension<T>(T self) where T : IEquatable<T>
{
public bool IsEqualTo(T other) => self.Equals(other);
}
} Is this an allowed syntax now? It would define the same method name with the same generic arguments but different where conditions. public static class EqualExtensions {
public static bool IsEqualTo<T>(this T self, T other) where T : INumber<T> => self == other;
public static bool IsEqualTo<T>(this T self, T other) where T : IEquatable<T> => self.Equals(other);
} Which would be awesome if it would work but this is illegal C# as of now. Maybe a perfect opportunity to resolve this with different |
Beta Was this translation helpful? Give feedback.
-
I was fiddling around with some nice new features like operator overloading which may or may not come. With that, I tried to "equip" a 3rd party type with a collection expression. But somehow it doesn't work: IReadOnlySet<int> s = [1, 2];
Console.Write(s.Count);
[CollectionBuilder(typeof(Extensions), nameof(Extensions.Create))]
public static class Extensions
{
extension<T>(IReadOnlySet<T>)
{
public IReadOnlySet<T> Create(ReadOnlySpan<T> items)
{
return new HashSet<T>();
}
}
} With error:
Given that this code is lowered to: [Extension]
[CollectionBuilder(typeof (Extensions), "Create")]
public static class Extensions
{
[Extension]
[SpecialName]
[return: Nullable(new byte[] {1, 0})]
public static IReadOnlySet<T> Create<T>([In] this IReadOnlySet<T> obj0, [Nullable(new byte[] {0, 1})] ReadOnlySpan<T> items)
{
return (IReadOnlySet<T>) new HashSet<T>();
}
[NullableContext(1)]
public sealed class <>E__0<[Nullable(2)] T>
{
[CompilerGenerated]
[SpecialName]
private static void <Extension>$([In] IReadOnlySet<T> obj0)
{
}
public IReadOnlySet<T> Create([Nullable(new byte[] {0, 1})] ReadOnlySpan<T> items)
{
throw null;
}
}
} It does seem like it should work - do I miss something here? |
Beta Was this translation helpful? Give feedback.
-
I love C# and there are not a lot of things that I miss in my day to day live. One thing I always thought was a bummer though is how the compiler struggles when inferring constraints with more than one type argument. The main place I ususally run into this "issue" is when writing extension methods like this: public interface ISubjectAssertion<TSubject> {
public TSubject Subject { get; }
}
public sealed class ExceptionAssertion : ISubjectAssertion<Exception> {
public required Exception Subject { get; init; }
public ExceptionAssertion HasMessage(string message) => this;
}
public static class AssertionExtensions {
public static TAssertion Match<TAssertion, TSubject>(
this TAssertion assertion,
Func<TSubject, bool> predicate
) where TAssertion : ISubjectAssertion<TSubject> {
// ...
return assertion;
}
} Here, when trying to call var assertion = new ExceptionAssertion { Subject = new Exception("Oh no! :(") };
assertion.Match(subject => subject is InvalidOperationException);
Of course you could just drop the type parameter for What I would love for the "extensions" feature is being able to specify "complex" constraints on the extension delcaration like: public static class AssertionExtensions {
extension<TAssertion, TSubject>(TAssertion assertion) where TAssertion : ISubjectAssertion<TSubject> {
public TAssertion Match(Func<TSubject, bool> predicate) {
// ...
return assertion;
}
} Right now, specifying more type arguments like this doesn't seem to be possible (although I might be missing something). Thank you for the amazing job you're doing with the language! 💖 |
Beta Was this translation helpful? Give feedback.
-
Maybe its just me but I always name my instance parameter @this for extensions because seeing the extension methods as an addon to the extended class, it should read similar. So when we just go on and use the normal this keyword for the captured instance, we can handle static and instance extensions with the same ONE syntax:
I would also prefer the mentioned syntax where 'class'-keyword gets replaced with 'extension' so the innerblock could be avoided completely:
|
Beta Was this translation helpful? Give feedback.
-
Proposal: Duck typing invocable #9314 |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
I thought I would throw in my 2 cents. I have the following dislikes for the current proposed syntax:
Here's an alternative syntax: Extension methods/properties public extension Extensions for IEnumerable<int> source {
...
}
Static extension methods/properties public static extension Extensions for List<T> {
...
}
Generics public extension Extensions<T> for IEnumerable<T> source where T : INumber<T> {
...
} What I like:
|
Beta Was this translation helpful? Give feedback.
-
Is this something we are considering? To validate public static class Extensions
{
extension(IEnumerable<int> source)
{
public IEnumerable<int> WhereGreaterThan(int threshold)
{
ArgumentNullException.ThrowIfNull(source);
return source.Where(x => x > threshold);
}
public bool IsEmpty()
{
ArgumentNullException.ThrowIfNull(source);
return !source.Any();
}
}
} Would be nice, if we can do this instead, public static class Extensions
{
extension(IEnumerable<int> source)
{
// No duplication
ArgumentNullException.ThrowIfNull(source);
public IEnumerable<int> WhereGreaterThan(int threshold)
=> source.Where(x => x > threshold);
public bool IsEmpty()
=> !source.Any();
}
} |
Beta Was this translation helpful? Give feedback.
-
Using the default implementation and extension members of an interface allows a class to obtain APIs in bulk in a manner similar to multiple inheritance. |
Beta Was this translation helpful? Give feedback.
-
Good afternoon, is it planned to extend the type by adding new interfaces? This would:
Syntax: public static class ListExtensions
{
extension(List<T> source) : IEquatable<List<T>>
{
public bool Equals(List<T> other)
{
return !(other is null) &&
(ReferenceEquals(source, other) || (source.Count == other.Count && source.SequenceEqual(other)));
}
}
} Benefits:
I would appreciate it if this opportunity is considered for addition. |
Beta Was this translation helpful? Give feedback.
-
Have we definitely lost the |
Beta Was this translation helpful? Give feedback.
-
Adding support for extension properties is a good idea. The rest of this is just needlessly complicating the language for minimal benefit. |
Beta Was this translation helpful? Give feedback.
-
I don't know whether this is a correct behavior or not: using System;
int integer = 42;
integer.Method("hello"); // okay
integer.Method<string>("hello"); // not okay
integer.Method<int, string>("hello"); // okay
file static partial class Program
{
extension<T1>(T1 instance)
{
public void Method<T2>(T2 another)
{
Console.WriteLine((instance, another).ToString());
}
}
} It seems that I should explicitly specify all two type parameters to make compiler happy... Is it by-design? |
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