Perhaps I am demonstrating my ignorance of some oft-used feautre of C# or the .NET framework, but I would like to know if there is a natively-supported way to create a type alia
If you look at the .NET Framework System.Uri is the closest example that is similar to an email address. In .NET the pattern is to wrap something in a class and add constraints that way.
Adding strong typing that adds additional constraints to simple types is an interesting language feature that I believe some functional language has. I can't recall the name of the language which would let you add dimensional units such as feet to your values and do a dimensional analysis on your equations to ensure that the units matched.
Does the System.Net.Mail.MailAddress class fit your needs, or at least "help"?
EDIT: It's not explicitly IEquatable or ISerializable, but you could easily enough add those in your own wrapper.
Some background on why string
is sealed:
From http://www.code-magazine.com/Article.aspx?quickid=0501091 :
Rory: Hey Jay, you mind if I ask you a couple questions? I'm already curious about some things. First of all, and this was brought up at one of the MSDN events I did this week, why is String sealed? Note: for VB.NET programmers, Sealed = NotInheritable.
Jay: Because we do a lot of magic tricks in String to try and make sure we can optimize for things like comparisons, to make them as fast as we possibly can. So, we're stealing bits off of pointers and other things in there to mark things up. Just to give you an example, and I didn't know this when I started, but if a String has a hyphen or an apostrophe in it [then] it sorts differently than if it just has text in it, and the algorithm for sorting it if you have a hyphen or an apostrophe if you're doing globally-aware sorting is pretty complicated, so we actually mark whether or not the string has that type of behavior in it.
Rory: So, what you're saying is that in the string world, if you didn't seal String there would be a whole lot of room for wreaking a lot of havoc if people were trying to subclass it.
Jay: Exactly. It would change the entire layout of the object, so we wouldn't be able to play the tricks that we play that pick up speed.
Here is the CodeProject article that you probably have seen before:
http://www.codeproject.com/KB/cs/expandSealed.aspx
So yeah, implicit operator is your only solution.
I guess I do not get why you want to have both strong types AND implicit string conversion at the same time. For me, one rules out the other.
I tried to solve the same problem for ints (you mention int in the title, but not in the question). I found that declaring an enum gives you a type-safe integer which needs to be explicitly cast from/to int.
Update
Enums may not be intended for open sets, but can still be used in such a way. This sample is from a compilation experiment to distinguish between the ID columns of several tables in a database:
enum ProcID { Unassigned = 0 }
enum TenderID { Unassigned = 0 }
void Test()
{
ProcID p = 0;
TenderID t = 0; <-- 0 is assignable to every enum
p = (ProcID)3; <-- need to explicitly convert
if (p == t) <-- operator == cannot be applied
t = -1; <-- cannot implicitly convert
DoProc(p);
DoProc(t); <-- no overloaded method found
DoTender(t);
}
void DoProc(ProcID p)
{
}
void DoTender(TenderID t)
{
}
I think you want to use extension methods. They allow you to extend a classes functionality without creating a new derived type.
I made this class to cover identical needs. This one is for the type "int" (I also have one for "string"):
public class NamedInt : IComparable<int>, IEquatable<int>
{
internal int Value { get; }
protected NamedInt() { }
protected NamedInt(int val) { Value = val; }
protected NamedInt(string val) { Value = Convert.ToInt32(val); }
public static implicit operator int (NamedInt val) { return val.Value; }
public static bool operator ==(NamedInt a, int b) { return a?.Value == b; }
public static bool operator ==(NamedInt a, NamedInt b) { return a?.Value == b?.Value; }
public static bool operator !=(NamedInt a, int b) { return !(a==b); }
public static bool operator !=(NamedInt a, NamedInt b) { return !(a==b); }
public bool Equals(int other) { return Equals(new NamedInt(other)); }
public override bool Equals(object other) {
if ((other.GetType() != GetType() && other.GetType() != typeof(string))) return false;
return Equals(new NamedInt(other.ToString()));
}
private bool Equals(NamedInt other) {
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(Value, other.Value);
}
public int CompareTo(int other) { return Value - other; }
public int CompareTo(NamedInt other) { return Value - other.Value; }
public override int GetHashCode() { return Value.GetHashCode(); }
public override string ToString() { return Value.ToString(); }
}
And to consume it in your case:
public class MyStronglyTypedInt: NamedInt {
public MyStronglyTypedInt(int value) : base(value) {
// Your validation can go here
}
public static implicit operator MyStronglyTypedInt(int value) {
return new MyStronglyTypedInt(value);
}
public bool Validate() {
// Your validation can go here
}
}
If you need to be able to serialize it (Newtonsoft.Json), let me know and I'll add the code.