Skip to content

Strongly-typed type aliases #58

@rcook

Description

@rcook

I would love to see strongly-typed type aliases in the C# programming language. These would be roughly equivalent to types declared in Haskell using newtype. Such aliases would be very different from regular aliases declared using using statements since the source-level type would ultimately be erased by the compiler. However, they would behave as distinct types from the original type during compilation time.

I think this would be very helpful for dealing with easily abused types such as string. Here's an example using a hypothetical new language structure using the newtype keyword:

using System;
newtype EmailAddress = System.String;

class Program
{
    static EmailAddress CreateEmailAddress(string text)
    {
        // Valid: use cast-style syntax to "convert" string to EmailAddress
        return (EmailAddress)text;
    }

    static void UseEmailAddress(EmailAddress emailAddress)
    {
        // Valid: everything has ToString
        Console.WriteLine(emailAddress);

        // Invalid: EmailAddress does not directly expose Length
        Console.WriteLine(emailAddress.Length);

        // Valid: EmailAddress instance can be explictly converted back to System.String
        // Does not result in runtime conversion since EmailAddress type is erased by
        // compiler
        Console.WriteLine(((string)emailAddress).Length);
    }

    static void Main()
    {
        // Valid
        UseEmailAddress(CreateEmailAddress("[email protected]"));

        // Invalid
        UseEmailAddress("[email protected]");
    }
}

This is purely syntactic sugar, however, and the compiler will emit all references to type EmailAddress as references to System.String instead. Perhaps some additional metadata could be applied to arguments of these alias types to provide a hint to compilers about the original (source-level) type assigned to it. There are obviously many questions that arise. The main one, I think, is what to do about methods that are declared on the original type: if they are all projected onto this new "virtual" type, then we lose all advantages of this new type safety. I believe that the compiler should prohibit the developer from calling any of the original type's members on a reference to the alias type without inserting an explicit cast back to the underlying type. Such a cast would be purely syntactic and would not incur any runtime overhead.

In traditional C# programming, the developer would most likely wrap a string inside an EmailAddress wrapper class to ensure type safety and to curate access to the underlying string. My proposed language feature would enable the developer to more elegantly express certain concepts without bloating the code with wrapper classes. Importantly, it would also allow generation of extremely efficient code.

More issues:

  • What is the scope of a type declared with newtype?

Perhaps newtype could look more like the definition of a class or struct:

namespace MyNewtypeDemo
{
    using System;

    // No inheritance: this is an alias
    // Only methods declared inside the "newtype" declaration
    // can access the members of "string": no casting required
    // We'll reuse the contextual keyword "value"
    public newtype EmailAddress : string
    {
        // Constructor: can assign to "value" which is the actual string
        // Can only initialize via constructor
        public EmailAddress(string text)
        {
            if (!IsValidEmailAddress(text))
            {
                throw new ArgumentException();
            }

            // No cast required since compiler knows that "value" is a string
            value = text;
        }

        // Length is not normally accessible but we can wrap call to value.Length
        public int Length
        {
            get { return value.Length; }
        }

        public string ToActualString()
        {
            return value;
        }

        private static bool IsValidEmailAddress(string text)
        {
            // Validate text etc.
            return true;
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions