Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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] Named generic type constraints #3813

Closed
Pyrdacor opened this issue Aug 21, 2020 · 7 comments
Closed

[Proposal] Named generic type constraints #3813

Pyrdacor opened this issue Aug 21, 2020 · 7 comments

Comments

@Pyrdacor
Copy link

Background

Let's assume you have a bunch of methods or classes which all use the same complex type constraints:

class Foo<T> where T : IFoo, IBar, IBaz, IDisposable, new() {}

class Bar<T> where T : IFoo, IBar, IBaz, IDisposable, new() {}

class Baz<T> where T : IFoo, IBar, IBaz, IDisposable, IXYZ, new() {}

Maybe I have many more. Now I want to add another constraint to all of them or remove one for all of them. I have to do a lot of work and maybe I miss some.

My idea is something like this:

using constraint MyFancyConstraint = IFoo, IBar, IBaz, IDisposable, new();


class Foo<T> where T : constraint MyFancyConstraint {}

class Bar<T> where T : constraint MyFancyConstraint {}

class Baz<T> where T : IXYZ, constraint MyFancyConstraint {}

The syntax could be improved of course.

It has two major advantages:

  • For many constraints it may be shorter (better readability)
  • You can manage constraints for many classes/methods at a central point (at least per file/namespace/class)

Implementation

I guess this could be a simple preprocessing step of the compiler where the named constraints are just replaced by the constraint list and then compiling starts as usual.

Either they are implemented like other usings (only in the current file) or shared between files. I guess first one would be easier and second one more useful. :)

Icying on the cake

It would be nice if constraints themselves could be generic like:

using constraint List<T> = IList<T>, IList, ICollection<T>;

public class List<T> : constraint List<T>, ...

Real world use case

The C# collections are a good example:

public class List<T> : System.Collections.Generic.ICollection<T>,
 System.Collections.Generic.IEnumerable<T>,
 System.Collections.Generic.IList<T>,
 System.Collections.Generic.IReadOnlyCollection<T>,
 System.Collections.Generic.IReadOnlyList<T>,
 System.Collections.IList {}

public class HashSet<T> : System.Collections.Generic.ICollection<T>,
 System.Collections.Generic.IEnumerable<T>,
 System.Collections.Generic.IReadOnlyCollection<T>,
 System.Collections.Generic.ISet<T>,
 System.Runtime.Serialization.IDeserializationCallback,
 System.Runtime.Serialization.ISerializable {}

...
using constraint Collection<T> = System.Collections.Generic.ICollection<T>,
 System.Collections.Generic.IEnumerable<T>,
 System.Collections.Generic.IReadOnlyCollection<T>;

public class List<T> : constraint Collection<T>,
 System.Collections.Generic.IList<T>,
 System.Collections.Generic.IReadOnlyList<T>,
 System.Collections.IList {}

public class HashSet<T> : constraint Collection<T>,
 System.Collections.Generic.ISet<T>,
 System.Runtime.Serialization.IDeserializationCallback,
 System.Runtime.Serialization.ISerializable {}

Better examples

If methods deal all with the same type of data they often use the same restrictions on the generic types. If the class is a non-generic one you may end up with many methods which all share the same constraints. You don't want to retype it for every method. This takes time and may be error-prone.

The same is true for classes which are designed for similar generic types.

You also can document named constraints better I guess.

@HaloFour
Copy link
Contributor

Without project-wide aliases this doesn't seem particularly useful. You end up moving the constraint to the top of the file but you can't really reuse it.

I also think that shapes might make such a proposal unnecessary as you could (theoretically) group the constraints into a "shape" and then use that shape as the constraint.

@Pyrdacor
Copy link
Author

That's a problem of usings in general. I would be glad if they would become more useful as well.

Is shapes gonna to be happen for sure? I would be fine with shapes if they can accomplish that.

@PathogenDavid
Copy link

Is shapes gonna to be happen for sure?

#1711 is marked as a C# 11.0 candidate, so I wouldn't hold my breath.


If this is something you find yourself needing frequently, this is almost a good candidate for source generators. Here's my doodle of what it might look like.

Basically instead of special syntax, you specify named constraint collections as dummy types and apply them with a special attribute:

class MyFancyConstraint<T>
    where T : IFoo, IBar, IBaz, IDisposable, new()
{}

[ApplyConstraint(typeof(MyFancyConstraint<>))]
public partial class Foo<T> {}

In response to that attribute, the source generator generates a partial piece of that class with the generic constraints.

I think it'd satisfy your requirements for the most part, but unfortunately this situation from your examples is somewhat problematic:

class Baz<T> where T : IXYZ, constraint MyFancyConstraint {}

The first temptation is to do something like this:

[ApplyConstraint(typeof(MyFancyConstraint<>))]
public partial class Baz<T>
    where T : IXYZ
{}

Unfortunately you'll be met with error CS0265: Partial declarations of 'Baz<T>' have inconsistent constraints for type parameter 'T'.

As a work-around you could either define a separate XYZConstraint template class or come up with a system of attributes to synthesize the needed constraints, effectively duplicating generic type constraint functionality.

It's not quite as pretty, but it's a solution you have access to today.

@Pyrdacor
Copy link
Author

C# 11? Candidate? That's pretty far away.

I don't like source generators much. This seems pretty easy to add to the language/compiler. But anyway thanks for the info.

@CyrusNajmabadi
Copy link
Member

This seems pretty easy to add to the language/compiler.

That's part of the decision making. But it's only really important if the feature is deemed to be a good addition in the first place.

@Pyrdacor
Copy link
Author

Proposals are usually created cause someone ran into an annoying situation multiple times and feels the need for something new. So "good addition" is very subjective.

@CyrusNajmabadi
Copy link
Member

So "good addition" is very subjective.

Indeed.

@dotnet dotnet locked and limited conversation to collaborators Dec 6, 2024
@333fred 333fred converted this issue into discussion #8825 Dec 6, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants