Strongly-typed type aliases
See original GitHub issueI 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("rcook@rcook.org"));
// Invalid
UseEmailAddress("rcook@rcook.org");
}
}
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;
}
}
}
Issue Analytics
- State:
- Created 9 years ago
- Reactions:3
- Comments:20 (9 by maintainers)
Top GitHub Comments
@MadsTorgersen this is unfortunate (at least). How do you accomplish it with struct? Either I am missing something or you would have to constantly type something like
wrapper.Value
.Anyway, it is 2022 and working with primitive types (like long, int) was a pain years ago and it is pain still. This is because I cannot differentiate that this
long
is id for nodes, and thislong
is id for roads, and thislong
… and so on. For C# they are all the same.@svick
Yes, you’re absolutely right. From a syntactic sugar standpoint, it doesn’t gain us much. However, my other interest in having such a language feature is in generating efficient runtime code. I’m happy to declare my
newtype
with syntax that is more or less the same as that of astruct
orclass
. However, I want use of the objects of this type to incur as little overhead as possible over using a rawstring
. Essentially, I want type safety at compile time and minimal code overhead at runtime.In many ways this is similar to some of the motivations behind
enum
types when a language implements them properly. These are essentially constrained integers. I want to be able to constrain strings, or any other type I can think of.Some links of interest: