Typedefs

Topics: C# Language Design
May 9, 2014 at 5:22 AM
I would like to see typedefs in C#. Often times it is useful to be able to refer to a thing by multiple names, especially when you want to migrate to a new naming convention without breaking old stuff.

Honestly, I really only want this so I can have UInt8 and Int8. I have to look those two up every time.
May 9, 2014 at 6:35 AM
Edited May 9, 2014 at 6:35 AM
I agree. Current using alias = ... file scoping is just too restricting. typedef's would greatly simplify dealing with nested generics.
May 9, 2014 at 10:35 AM
I agree. Granted, I don't need typedefs nearly as often in C# as I do in C++, but it when I do need it...

It ought to be quite simple to implement. It should just be another entry in the symbol table.
May 9, 2014 at 9:32 PM
Edited May 9, 2014 at 9:45 PM
I agree. One use case I have is when emulating discriminated unions in C#. I use an implementation inspired by http://stackoverflow.com/a/3199453/154766 , where for example I can have a method with such return type as:
Union3<ResultA, ResultB, ResultC> Foo();
Now all implementors of this interface must write that terrible looking type, and object construction looks like:
new Union3<ResultA, ResultB, ResultC>.Case1(new ResultA(...));
The only solution to that problem currently is to declare the same alias everywhere, but it's a convention that cannot be enforced. Note that in this case declaring a dummy class won't do the trick, as it won't be convertible to the union cases, i.e. this
abstract class FooResult : Union3<ResultA, ResultB, ResultC>
So there is no way of achieving this well in C# currently. I would like to promote the use of discriminated unions in our code base but this single issue makes it quite problematic.
May 10, 2014 at 6:31 PM
And if you're renaming the type, why not the members as well.
typedef PersonInfo = Tuple<string, string, int>
{
   Name as Item1;
   Address as Item2;
   Age as Item3;
}
May 10, 2014 at 6:37 PM
For the last example from TheGrandUser, you could instead just do this (currently):
class PersonInfo : Tuple<String, String, UInt16>
{
    public String Name { get { return Item1; } set { Item1 = value; } }
    public String Address { get { return Item2; } set { Item2 = value; } }
    public UInt16 Age { get { return Item3; } set { Item2 = value; } }
}
Then you could still use it as a Tuple, or you could use it as a PersonInfo. That being said, the above syntax is definitely simpler.
May 11, 2014 at 2:51 PM
Micah071381 wrote:
Then you could still use it as a Tuple, or you could use it as a PersonInfo. That being said, the above syntax is definitely simpler.
That would not allow the type to be used interchangeably as a suitable tuple or PersonInfo. An instance of PersonInfo could be given to code expecting a Tuple, but not vice versa.
May 16, 2014 at 2:30 PM
supercat wrote:
An instance of PersonInfo could be given to code expecting a Tuple, but not vice versa.
It could if you defined an explicit/implicit conversion operator from Tuple<...> to PersonInfo, which again is far more cumbersome than the typedef being proposed.
May 16, 2014 at 4:03 PM
Ultrahead wrote:
It could if you defined an explicit/implicit conversion operator from Tuple<...> to PersonInfo, which again is far more cumbersome than the typedef being proposed.
I really dislike conversions that return new class-type objects. It's too late now, but if I had my druthers, the only boxing conversions would be those where a value type was passed to a method parameter which had an attribute expressly authorizing boxing, or those which were explicitly requested at the boxing site [being able to write String.Format("The value is {0}", someInteger) is nice, but String.Format is expected not to be particularly efficient, and so could have an "enable boxing" attribute]. Boxing or other representation-changing reference-type conversions should be avoided when possible, but should be done as early as possible when unavoidable. Making such conversions too easy will result in their being done needlessly in many loops where they could and should be hoisted outside. Obviously it's too late to change the way boxing works, but I dislike the idea of adding more conversions which would pose the exact same issue.
May 16, 2014 at 4:30 PM
supercat wrote:
I really dislike conversions that return new class-type objects. It's too late now, but if I had my druthers, the only boxing conversions would be those where a value type was passed to a method parameter which had an attribute expressly authorizing boxing, or those which were explicitly requested at the boxing site [being able to write String.Format("The value is {0}", someInteger) is nice, but String.Format is expected not to be particularly efficient, and so could have an "enable boxing" attribute]. Boxing or other representation-changing reference-type conversions should be avoided when possible, but should be done as early as possible when unavoidable. Making such conversions too easy will result in their being done needlessly in many loops where they could and should be hoisted outside. Obviously it's too late to change the way boxing works, but I dislike the idea of adding more conversions which would pose the exact same issue.
Just to be clear, I was suggesting typedefs as a compiler feature. All the typedef would do is at compile time change the type that is written to CIL to the root type.
class Zuul {}
alias Foo to Zuul;
alias Bar to Foo;

class Program
{
    void Main()
    {
        Bar myBar = new Bar();
    }
}
The above Main method would result in CIL that looks something like:
newobj instance void [MyAssembly]Zuul::.ctor()
stloc.0
Notice how there is no Foo or Bar, there is only Zuul.
May 16, 2014 at 4:38 PM
Edited May 16, 2014 at 4:39 PM
@Micah071381: thanks for the clarification. You have my +1 to your suggestion.

@supercat: I wasn't saying it should be done that way, only pointing out that it could be achieved through conversion operators. For the example of Tuples above, there would be no boxing involved, but you would need to check for nullity at runtime, which is not what typedef would be used for.
May 16, 2014 at 4:53 PM
Ultrahead wrote:
@supercat: I wasn't saying it should be done that way, only pointing out that it could be achieved through conversion operators. For the example of Tuples above, there would be no boxing involved, but you would need to check for nullity at runtime, which is not what typedef would be used for.
My intended point was that using implicit type conversion to make code which would behave as it would with aliases, but much slower, should not be considered a way of achieving the desired effect with existing means. I know that the tuples wouldn't technically involve boxing, but from a semantic difference what matters is not the particular mechanism via which a new heap object is created to encapsulate data, but rather the fact that every time the data is needed in a particular for, a new heap object will get created.
May 16, 2014 at 4:57 PM
@supercat: understood. You're right, a new heap object is then created.
May 16, 2014 at 11:45 PM
Edited May 16, 2014 at 11:52 PM
I also vote for type aliases resolved at compile time. Typically there is an "id" spread in the code that is of type int. Later you can decide to change it to long or whathever else. Having a MyID type alias would make it more clear. On the other hand, the scope of the type alias should go across assembly boundary to be truely useful that needs to extend metadata/reflection information. Is it still type safe ?
May 17, 2014 at 5:42 PM
pvones wrote:
I also vote for type aliases resolved at compile time. Typically there is an "id" spread in the code that is of type int. Later you can decide to change it to long or whathever else. Having a MyID type alias would make it more clear. On the other hand, the scope of the type alias should go across assembly boundary to be truely useful that needs to extend metadata/reflection information. Is it still type safe ?
Way back when, another thing I wished for with type aliases would have been the ability to have an alias define extension methods. For example, extension methods could be defined on a ConnectionString type which was an alias of String, without having to make such extension methods applicable to all strings everywhere. Beyond the fact that such scoping would avoid having extension methods be accidentally invoked in places they're not wanted, it would also allow extension methods to shadow existing methods. For example:
alias type HexInt32 : Int32  // Assuming `type` is a reserved word
{
  new String ToString() { return base.ToString("X8"); }
}
With the existing extension-method mechanism, extension methods are only used when no other method is available. If the aformentioned style of aliasing were available, the fact that an alias was used rather than the original type would imply a desire to use extension methods associated with the alias. Additionally, implicit conversions could be added for an alias, or forbidden (by adding conversion tagged with "Obsolete").

Not sure anyone else would like such an idea, but I think it would have quite a few usage cases [I especially like the idea of being able to shadow ToString and control conversions. If I could, I'd often use aliases of Single and Double which would allow implicit Double->Single but "obsolete" Single->Double, and reject cases where the existence of bidirectional conversions between Single and Double would create ambiguity. Such behavior can't be imposed on existing code that uses those types, but would cause the compiler to allow things like:
const Real OneTenth = 0.1f;   // Alias for Double
shortReal f2 = f1 * OneTenth;  // Likely faster than f1 / 10.0f, and more accurate than f1 * 0.1f;
Real d2 = d1 * OneTenth;
but correctly squawk at
const shortReal OneTenth = 0.1f; // Alias for Single
shortReal f2 = f1 * OneTenth;
Real d2 = d1 * OneTenth;  // Erroneously divides by 9.99999985098839 rather than ten.