Extend using directives to support generics

Topics: C# Language Design
May 26, 2014 at 4:44 PM
Edited May 26, 2014 at 4:45 PM
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.
This proposal 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.
May 26, 2014 at 4:47 PM
Edited May 26, 2014 at 4:52 PM

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.
May 26, 2014 at 6:42 PM
Edited May 26, 2014 at 6:44 PM
I found myself wanting this feature just a couple of days ago. I had a code file where I had declared a class C, and I wanted to explicitly make Func<C, Bool> available, but it doesn't allow such using declarations, which forced me to explicitly say System.Func<C, Bool> everywhere (short of just using System, which I was explicitly trying to avoid because I had type names which were ambiguous with others in the System namespace, namely, Type.)
May 27, 2014 at 5:22 AM
I would like to see a feature like this:
using List<T> = System.Collections.Generic.List<T>;
using Dictionary<K, V> = System.Collections.Generic.Dictionary<K, V>;
using StringKeyDictionary<V> = System.Collections.Generic.Dictionary<string, V>;
No need for arity inference, more familiar, better readable syntax. It's a purely syntactical feature, so I see no problems with partially-bound type aliases.
May 27, 2014 at 5:26 AM
sparkie360 wrote:
I found myself wanting this feature just a couple of days ago. I had a code file where I had declared a class C, and I wanted to explicitly make Func<C, Bool> available, but it doesn't allow such using declarations, which forced me to explicitly say System.Func<C, Bool> everywhere (short of just using System, which I was explicitly trying to avoid because I had type names which were ambiguous with others in the System namespace, namely, Type.)
Why not use usual alias using MyFunc = System.Func<C, Boolean>;?
May 27, 2014 at 4:38 PM
Quinisext wrote:
Why not use usual alias using MyFunc = System.Func<C, Boolean>;?
Ok, that's pretty stupid of me. I tried just this and it reported the error that C was not found, and I mistakenly assumed it was because C was not defined yet. Turns out I just needed to include the namespace in the alias. System.Func<MyNamespace.C, Bool>.
May 28, 2014 at 7:51 PM
What about this, building off of sparkie360's example:
using MyFunc<T> = System.Func<T, Boolean>;
I find myself doing this quite a bit, although at my job where I work with C++, where the syntax is (new feature in C++11, I believe)
template<typename T>
typedef Foo<T, bool> Bar;
which is the same as proposed C# syntax
using Bar<T> = Foo<T, bool>;
Example of usefulness (in C++ at least):
Say I define a template (generic) class Foo, that's intended to be passed around as a shared_ptr. Instead of forcing the user to type out shared_ptr<Foo<T>> all the time, I would do
template<typename T>
typedef shared_ptr<Foo<T>> FooPtr;
Other, more C#-ish useful things might be defining a System.Predicate-like delegate, but instead of bool, having int (for whatever reason):
using IntPred<T> = System.Func<T, int>;
May 29, 2014 at 2:33 PM
Quinisext wrote:
I would like to see a feature like this:
using List<T> = System.Collections.Generic.List<T>;
using Dictionary<K, V> = System.Collections.Generic.Dictionary<K, V>;
using StringKeyDictionary<V> = System.Collections.Generic.Dictionary<string, V>;
No need for arity inference, more familiar, better readable syntax. It's a purely syntactical feature, so I see no problems with partially-bound type aliases.
+1

khyperia wrote:
What about this, building off of sparkie360's example:
using MyFunc<T> = System.Func<T, Boolean>;
+1, also.
May 29, 2014 at 6:32 PM
Edited May 29, 2014 at 6:33 PM
The problem with the proposals regarding partial substitution is you have to answer questions like the following.
  1. How are generic type constraints handled? Unchecked until the point of use?
  2. What are the limits of generic type substitution?
    • Multiple references?
      using Foo<T> = System.Collections.Generic.Dictionary<T, T>;
      
    • Nested generics?
      using Foo<T> = System.Collections.Generic.Dictionary<T, System.Collections.Generic.IEnumerable<T>>;
      
    • Nullable types?
      using Foo<T> = System.Collections.Generic.KeyValuePair<T, T?>;
      using Foo2<T> = System.Collections.Generic.KeyValuePair<T, Nullable<T>>;
      
Since the current complete inability to specify an unbound generic type forces developers to either fully quality the name, make up non-generic names for specific instantiations, or use a namespace using, I felt the introduction of a simple syntax for the primary limitation is the most straightforward way to see this addressed in the language. Since we already have a standard syntax for referencing unbound generic types for a typeof expression, it seemed natural to use the same syntax for referring to unbound generic types in a using alias declaration.
May 29, 2014 at 8:49 PM
Edited May 29, 2014 at 9:49 PM
sharwell wrote:
How are generic type constraints handled? Unchecked until the point of use?
Maybe we could use the where clause (generic type constraint).

sharwell wrote:
What are the limits of generic type substitution?
With the help of the where clause, we could have the same limits we have when declaring a generic type.

For example:
using Foo<T> = System.Collections.Generic.KeyValuePair<T, T?> where T: struct;
May 30, 2014 at 12:23 PM
sharwell wrote:
Multiple references?
Sure, why not?
Nested generics?
Again, why not?
Nullable types?
Nope, it won't be just an alias anymore.

Postponing constraints checking until a point of use works and it makes things simple but not so elegant. So partial checking should be done at the point of declaration.
May 30, 2014 at 12:33 PM
Y'all have taken a simple and fully-defined proposal to address a commonly encountered limitation in the language, and turned it into something vastly more complicated that may or may not help people. In addition, the changes negatively impact the elegance of the original solution to a specific problem. Anything beyond the simple ability to use a using alias directive with a specific unbound generic type should really be a separate proposal.