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

Extend using directives to support unbound generics #116

Closed
sharwell opened this issue Jan 28, 2015 · 12 comments
Closed

Extend using directives to support unbound generics #116

sharwell opened this issue Jan 28, 2015 · 12 comments
Labels

Comments

@sharwell
Copy link
Member

I propose extending the Using alias directives feature of C# as follows.

using-alias-directive:
    using identifier = namespace-or-type-name ;
    using identifier generic-dimension-specifier = unbound-type-name ;

The generic-dimension-specifier and unbound-type-name productions are defined in the C# Language Specification description of the typeof operator.

The semantics of an unbound using alias directive are almost identical to the semantics of using alias directives as we know them; they merely provide the ability to associate a generic arity to the alias. The following additional requirements would be in effect:

  • It is a compile-time error if the generic-dimension-specifier and unbound-type-name do not have the same generic arity.

Since this proposal is intended to solve a specific limitation in the support for using directives (for which there is no real workaround), it is written in the simplest form possible. It neither provides nor prevents a future expansion of the language from supporting partially-bound type parameters in a using alias directive; I believe this is a problem for another time.

Example

C# currently allows the following using statements:

using System.Collections.Generic;
using MyList = System.Collections.Generic.List<string>;

This expansion would additionally allow the following:

using List<> = System.Collections.Generic.List<>;

One might argue that the duplication of <> on the left and right sides would be unnecessary. I believe the syntax should appear this way for the following reasons:

  1. With the exception of var, C# consistently requires types to specify generic type arguments or generic arity when referencing types. Using inference in this specific case would be misleading, especially for the following:

    using List = System.Collections.ArrayList;
    using List = System.Collections.Generic.List<>; // arity inference here is confusing
  2. Arity inference could cause future challenges to a language extension designed to support partially-bound type parameters in a using alias directive.

@sharwell
Copy link
Member Author

@leppie That would be a partially-bound generic directive. I'm concerned that pushing for such a feature as part of this request would further delay fixing what I see as a major oversight in the specification of using alias directives. The only thing I'm requesting is the ability to reference unbound generic types.

📝 My original request does mention partially-bound types, but only for the sake of suggesting that my proposed syntax is least likely to interfere with the design of such a feature in the future.

@leppie
Copy link
Contributor

leppie commented Jul 17, 2015

I realised I made a mistake, on that could not be aliased at all:

class Cons<A, D> { }
class List<T> : Cons<T, List<T>> { }

Trying to alias List<T> would lead to an infinite expansion:

using List<T> = Cons<T, List<T>>;

It would be nice though to define equivalent types like that without using inheritance. I have no idea how though ;p

@HaloFour
Copy link

I would prefer to include the generic type parameter in the alias. I would also like to see the alias support partially bound generics. Recursion could be an issue, but to shut that down I would say that aliases cannot refer to themselves (or perhaps other aliases).

using StringDictionary<T> = Dictionary<String, T>;

@sharwell
Copy link
Member Author

I would prefer to include the generic type parameter in the alias.

This has multiple downsides.

  1. It's not clear that partially-bound generics are not supported. C# does not allow partially-bound generics at all, so requesting them is a brand new feature. On the other hand, C# does allow using alias directives for any type except an unbound generic type. This feature request addresses a very specific deficiency in a well-established feature of the language.
  2. Assuming that users would want to use the same identifiers to refer to generic type parameters as appear in the original signature, the editor experience would be difficult to impossible to get right. The entire burden of typing the correct identifiers on the left-hand-side of = falls on the developer. Typing Dictionary<,> is much easier than typing Dictionary<TKey, TValue>.
  3. If we allow identifiers to be specified on the left-hand-side, new rules need to be created regarding what identifiers may be used here.
  4. If identifiers are included, it is less clear that the type being referred to is an unbound generic type. C# already has a syntax for referring to unbound generic types, and reusing it here simplifies the feature specification.

I would also like to see the alias support partially bound generics.

This is a separate feature request. While related to this one, I think it should be filed as a separate issue.

@HaloFour
Copy link

  1. The rules would be no different than consuming a generic type in general, or inheriting from a generic type. If that's confusing then we have a much bigger problem.
  2. A character, at most, required per argument. The developer can pick and choose whether or not they want to keep the same parameter name. In my opinion, it's much clearer than simply ignoring them and pretending that they exist. And given the use cases that it satisfies the developer will save more keystrokes in the long run anyway.
  3. Going to need to change the syntax anyway. The rules can be the same for defining any type identifier.
  4. Why? If anything I think keeping the syntax appear completely unbounded is more confusing looking. Yes, C# has existing syntax for this, which developers see significantly less often than they see actual generic types. Dictionary<TKey, TValue> is more familiar than Dictionary<,> especially given the commas are required. Any developer familiar with the latter is going to be as familiar with the former.

This is a separate feature request.

You cannot bring this up without having these specific features mentioned. If aliases are to be enhanced to support generics in any form I see no reason why these specific capabilities would be segregated. That is an incredibly arbitrary delineation.

http://roslyn.codeplex.com/discussions/575542
http://roslyn.codeplex.com/discussions/577015
http://roslyn.codeplex.com/discussions/546623

@sharwell
Copy link
Member Author

A character, at most, required per argument.

This is still more than zero characters per argument, especially considering you can use zero characters and always match names with the original definition.

Why? If anything I think keeping the syntax appear completely unbounded is more confusing looking.

The only case where Dictionary<TKey, TValue> can be used as a reference to Dictionary<,> is in a documentation comment:

/// <seealso cref="Dictionary&lt;TKey, TValue&gt;"/>

In all other cases, Dictionary<TKey, TValue> is either a constructed open generic type (where TKey and/or TValue are type parameters visible in the current scope), a closed generic type (where TKey and TValue are the names of types, not type parameters), or is the declaration of the type itself.

📝 The preferred syntax for referencing generic types in documentation comments is actually the following:

/// <seealso cref="Dictionary{TKey, TValue}"/>

@HaloFour
Copy link

And:

public class StringDictionary<TValue> : Dictionary<String, TValue> { }

People are already used to writing stuff like this. And again, that, right there, is of the most common* use cases that comes up when mentioning adding generic support to aliases.

* Anecdotal given conversations on these forums plus stackoverflow.

@sharwell
Copy link
Member Author

public class StringDictionary<TValue> : Dictionary<String, TValue> { }

In this example, Dictionary<String, TValue> does not refer to the unbound generic type, i.e. typeof(Dictionary<,>). Even if you change StringDictionary<TValue> to be StringDictionary<TKey, TValue>, it would still refer to a bound but open generic type constructed from two other generic type parameters.

This is actually an important distinction. Consider the following:

using Alias<,> = System.Collections.Generic.Dictionary<,>;

// in a method, this assertion should succeed:
Assert.AreSame(typeof(Alias<,>), typeof(System.Collections.Generic.Dictionary<,>));

@HaloFour
Copy link

In this example, Dictionary<String, TValue> does not refer to the unbound generic type

Why does it need to? Is that only because "unbound" is in the title of this proposal?

I get that you're trying to solve the simple case and avoid the complications. My concern is that solving this in this manner now does complicate solving it for partially bound in the future as you would probably end up with multiple syntaxes.

As for those complications, I've confirmed that C# already doesn't permit an alias to reference another alias, including itself. Keep that rule in place and the major issues regarding generic type parameters disappear.

using L1 = System.Collections.Generics.List<L>; // illegal
using I32 = System.Int32;
using L2 = System.Collections.Generics.List<I32>; // also illegal

@HaloFour
Copy link

Per your request I have opened a separate proposal. Feel free to beat it up.

I fully recognize that this would involve more effort and likely has more complications than I've enumerated but I'd rather follow the path and see where it leads. Shake as many issues out into the open. Maybe it's not as bad as we think.

@gafter
Copy link
Member

gafter commented Nov 20, 2015

I prefer the syntax proposed in #3993

@gafter
Copy link
Member

gafter commented Dec 4, 2015

Closing in favor of #3993.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants