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: Infer generic constrants #8041

Closed
iskiselev opened this issue Jan 20, 2016 · 14 comments
Closed

Proposal: Infer generic constrants #8041

iskiselev opened this issue Jan 20, 2016 · 14 comments

Comments

@iskiselev
Copy link

I was originally posted this suggestion in #7763, but looks like it should be separated and discussed here.
When we create any generic type/method, based on other generic type, we need to copy all generic constraints from original type. I suggest automatically infer such generic constraints implicitly, so that developer could write only additional constraints.
Suppose we have:

public class Foo<T>
  where T : IEnumerable
{}

now, every time we want to right method, that accept Foo<T> we need to repeat constraint where T : IEnumerable.
With this we can:

public static class FooHelper{
  // We still have where T : IEnumerable
  // that was added implicitly
  public static void DoSomething<T>(this Foo<T> input)
  {}

  // Here we add additional constraint. 
  // We still have where T : class, IEnumerable
  // but second part was added implicitly
  public static void DoSomethingOther<T>(this Foo<T> input)
    where T : class
  {}
}
@iskiselev iskiselev changed the title Feature request: Infer generic constrants Proposal: Infer generic constrants Jan 20, 2016
@HaloFour
Copy link

So a developer would not be able to see that DoSomethingOther has multiple constraints short of looking up the declaration of Foo<T>? And if Foo<T> inherits from Bar<T> with additional constraints the developer would have to look that up as well? And if DoSomethingOther calls some random methods that have their own constraints that could add another set of constraints? While I understand that having to specify all of the same/compatible constraints is a little obnoxious, I'd rather be able to see exactly what that one method requires in one place.

I think that rather than silently copying the constraints from any dependencies that I'd like to see Visual Studio provide a code fix to add the constraints to the signature of the method or class that is taking dependency on those other generic constraints.

@dsaf
Copy link

dsaf commented Jan 20, 2016

@HaloFour

So a developer would not be able to see ... short of looking up the declaration of ...?

I agree with your sentiment, but actually something like this is already happening with custom attributes #711. They can be inherited automatically without developer being fully aware of thier presence in the first place https://msdn.microsoft.com/en-us/library/system.attributeusageattribute.inherited(v=vs.110).aspx.

@HaloFour
Copy link

@dsaf Sure, but custom attributes don't affect the signature of the targeted class/method. This would, and having the generic constraints be inferred could easily lead to an accidental break in contract leading to a TypeLoadException. I'd prefer the explicit opt-in, which I don't think would be a bad thing if VS would complain about it and offer a quick code fix that would add the constraint. Then at least the developer doesn't have to type anything, or could quickly see and reevaluate their code.

@dsaf
Copy link

dsaf commented Jan 20, 2016

@HaloFour for me personally custom attributes constitute a part of signature (despite any general consensus) even if in some more philosophical sense, but I don't want to sway the discussion, it's not really important here.

@iskiselev
Copy link
Author

@HaloFour, It could not lead to contract breaking, as it is still controlled on compilation. Moreover, all productivity tools can still show them (in tooltips, object browser, etc...).
But I agree that it is valid, that omitting part of signature may be arguable. But really, as constraint are collecting only from other signature elements (types), you cannot break them at all.

In my example, you simple can't create Foo<T> that will break constraint of DoSomething<T>. So, you either have instance of Foo<T> and could path it to DoSomething<T>, or not have instance of Foo<T> at all. All such constraint give you zero information and only add some noise to your sources.

@HaloFour
Copy link

@iskiselev If other assemblies consume that assembly and a generic constraint was silently added that could break the contract as the CLR would throw an exception when enforcing that constraint at runtime.

@iskiselev
Copy link
Author

@HaloFour, are you talking about drop-in replacing assembly in already compiled application? I don't understand how source will help you in that case.
Otherwise, when you compile your assembly you will see all violation at compile time - as nothing change for compiled assembly. It still have all constraints.

@HaloFour
Copy link

@iskiselev

The two concerns are different. In the case of breaking contracts, yes, I am referring to replacing a dependency in an already deployed application. Such can be very problematic for any system that works with plugins.

My concern regarding the source code is that you can't immediately see the constraints as a part of the type/method signature.

@iskiselev
Copy link
Author

@HaloFour, why do you really need this see all this constraints? If we will talk a little bit more wide, method argument list is a list of constraints for method call, as you can't call method with arguments that don't match it's signature. At the same time, you don't write for each class full list of all it's base classes.

Same with generic signature:

public class Foo<T>:Boo
  where T : IEnumerable

When you've declared Foo, you said, that its' generic parameter must implement IEnumerable. When you try to call method, that is declared as: public static void DoSomething<T>(Foo<T> input), you must pass input that is (or derived) from Foo<T>. This method doesn't add any new constraints that are not defined in method argument list already. Repeating such constraints looks for me same as writing full class hierarchy in argument list:

public static void DoSomething<T>(this Foo<T>:Boo:object input)
  where T:IEnumerable

Thankfully, we don't need write Foo<T>:Boo:object each time when we need declare Foo<T> argument. On other side, it may end with breaking change, if somebody will change base class for Foo<T> to AnotherBoo.

@HaloFour
Copy link

@iskiselev Foo<T> is narrower than either Boo or object so by accepting Foo the signature already declares constraints that satisfy those requirements. The same is true with generic constraints. If the generic constraint on the method or derived class is narrower than the generic type arguments of the types that it accepts/uses then you are not required to repeat them. For example, if you add a generic type constraint of IList to DoSomething<T> then you aren't required to specify the constraint IEnumerable.

@iskiselev
Copy link
Author

@HaloFour, I've read your origanal comment once more and like to clarify only one additional thing: if DoSomething calls some random methods that have their own constraints, you still need to write such constraint in DoSomething signature, as only other signature elements may be used for auto-inferring constraints by my proposal. All other method calls will be inside method body, and not in signature.

@alrz
Copy link
Member

alrz commented Jan 20, 2016

This is the worse case of constraint inheritance.

http://ericlippert.com/2013/07/15/why-are-generic-constraints-not-inherited/

@iskiselev
Copy link
Author

I haven't thought that class signatures constraints may introduce circular reference. Looks like example with Banana, Coffee and Giraffe in Eric Lipert's article tries to create impossible hierarchy, so it is not fully honest. Even in that example, tool can show and prove it much easier, than human. So, for me it sounds even as additional pro argument for this feature.
Last example:

class Frob<T> where T : struct {}
class Blob<U> { Frob<U> frobu; }

Will be treated as compilation error per this suggestion, as only signature affect auto-inffer of generic constraints. So, you still need write:

class Frob<T> where T : struct {}
class Blob<U> where T : struct {} { Frob<U> frobu; }

but next will work:

class Frob<T> where T : struct {}
class Blob<U>: Frob<U> {}

At the same time, circular reference could really add complication to this, so probably, this should work only with method signatures, especially, as it already works with override methods.

@gafter
Copy link
Member

gafter commented Mar 20, 2017

We are now taking language feature discussion on https://github.com/dotnet/csharplang for C# specific issues, https://github.com/dotnet/vblang for VB-specific features, and https://github.com/dotnet/csharplang for features that affect both languages.

@gafter gafter closed this as completed Mar 20, 2017
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

5 participants