Reputation: 1937
I want to create my own EMailAddress class that acts like a string class.
So I like to do this
private EMailAddress _emailAddress = "[email protected]";
instead of
private EMailAddress _emailAddress = new EMailAddress("[email protected]");
Is there any way to accomplish what I want, or do I need to use the second alternative. Since string is sealed I can't use that, and the = operator can't be overloaded so I am out of ideas how to fix this....
Upvotes: 27
Views: 21376
Reputation: 77
As a follow-up to @Thorarin's code:
You could use this as base code and add extra features that a normal string class would have:
public class VarChar
{
private string _content;
public VarChar(string argContent)
{
_content = argContent;
}
public static implicit operator VarChar(string argContent)
{
if (argContent == null)
return null;
return new VarChar(argContent);
}
public static implicit operator string(VarChar x) => x._content;
public override string ToString()
{
return _content;
}
}
So the following code just works:
VarChar tempVarChar = "test";
string tempTest = tempVarChar; // = "test"
var tempText = tempVarChar.ToString();
tempVarChar = tempVarChar + "_oke";
tempText = tempVarChar; // = "test_oke"
Upvotes: 0
Reputation: 1273
I think that this question is a specific case of a more general question about creating type safety across a C# application. My example here is with 2 types of data: Prices and Weights. They have different units of measure so one should never try an assign a price to a weight or vice versa. Both under the covers are really decimal values. (I am ignoring the fact that there could be conversions like pounds to kg etc.) This same idea could be applied to strings with specific types like EmailAddress and UserLastName.
With some fairly boiler plate code one can make either explicit conversion or implicit conversions back and forth between the specific types: Price and Weight, and the underlying type Decimal.
public class Weight
{
private readonly Decimal _value;
public Weight(Decimal value)
{
_value = value;
}
public static explicit operator Weight(Decimal value)
{
return new Weight(value);
}
public static explicit operator Decimal(Weight value)
{
return value._value;
}
};
public class Price {
private readonly Decimal _value;
public Price(Decimal value) {
_value = value;
}
public static explicit operator Price(Decimal value) {
return new Price(value);
}
public static explicit operator Decimal(Price value)
{
return value._value;
}
};
With the "explicit" operator overrides, one gets a more restrictive set of things one can do with these classes. You have to be manually case every time you change from one type to another. For example:
public void NeedsPrice(Price aPrice)
{
}
public void NeedsWeight(Weight aWeight)
{
}
public void NeedsDecimal(Decimal aDecimal)
{
}
public void ExplicitTest()
{
Price aPrice = (Price)1.23m;
Decimal aDecimal = 3.4m;
Weight aWeight = (Weight)132.0m;
// ok
aPrice = (Price)aDecimal;
aDecimal = (Decimal)aPrice;
// Errors need explicit case
aPrice = aDecimal;
aDecimal = aPrice;
//ok
aWeight = (Weight)aDecimal;
aDecimal = (Decimal) aWeight;
// Errors need explicit cast
aWeight = aDecimal;
aDecimal = aWeight;
// Errors (no such conversion exists)
aPrice = (Price)aWeight;
aWeight = (Weight)aPrice;
// Ok, but why would you ever do this.
aPrice = (Price)(Decimal)aWeight;
aWeight = (Weight)(Decimal)aPrice;
NeedsPrice(aPrice); //ok
NeedsDecimal(aPrice); //error
NeedsWeight(aPrice); //error
NeedsPrice(aDecimal); //error
NeedsDecimal(aDecimal); //ok
NeedsWeight(aDecimal); //error
NeedsPrice(aWeight); //error
NeedsDecimal(aWeight); //error
NeedsWeight(aWeight); //ok
}
Just changing the "explicit" operators to "implicit" operators by replacing the words "explicit" with "implicit" in the code, one can convert back and forth to the underlying Decimal class without any extra work. This makes Price and Weight behave more like a Decimal, but you still cannot change a Price to a Weight. This is usually the level of type safety I am looking for.
public void ImplicitTest()
{
Price aPrice = 1.23m;
Decimal aDecimal = 3.4m;
Weight aWeight = 132.0m;
// ok implicit cast
aPrice = aDecimal;
aDecimal = aPrice;
// ok implicit cast
aWeight = aDecimal;
aDecimal = aWeight;
// Errors
aPrice = aWeight;
aWeight = aPrice;
NeedsPrice(aPrice); //ok
NeedsDecimal(aPrice); //ok
NeedsWeight(aPrice); //error
NeedsPrice(aDecimal); //ok
NeedsDecimal(aDecimal); //ok
NeedsWeight(aDecimal); //ok
NeedsPrice(aWeight); //error
NeedsDecimal(aWeight); //ok
NeedsWeight(aWeight); //ok
}
When doing this for String instead of Decimal. I like the idea Thorarin's answer about checking for null and passing null back in the conversion. e.g.
public static implicit operator EMailAddress(string address)
{
// Make
// EmailAddress myvar=null
// and
// string aNullString = null;
// EmailAddress myvar = aNullString;
// give the same result.
if (address == null)
return null;
return new EMailAddress(address);
}
To get these classes working as keys to Dictionary collections, you will also need to implement Equals, GetHashCode, operator ==, and operator !=
To make all of this easier, I made a class ValueType which I can extend, The ValueType class calls the base type for everything but the conversion operators.
Upvotes: 5
Reputation: 113392
Example:
public class EmailAddress
{
private string _value;
private static bool IsValidAddress(string address)
{
//whether to match RFC822 production or have something simpler,
//but excluding valid but unrealistic addresses, is an impl. choice
//out of scope.
return true;
}
public EMailAddress(string value)
{
if(value == null)
throw new ArgumentNullException();
if(!IsValidAddress(value))
throw new ArgumentException();
_value = value;
}
public EmailAddress(Uri uri)
{
if(value == null)
throw new ArgumentNullException();
if(!uri.Scheme != "mailto")
throw new ArgumentException();
string extracted = uri.UserInfo + "@" + uri.Host;
if(!IsValidAddress(extracted))
throw new ArgumentException();
_value = extracted;
}
public override string ToString()
{
return _value;
}
public static implicit operator EMailAddress(string value)
{
return value == null ? null : new EMailAddress(value);
}
public static implicit operator EMailAddress(Uri uri)
{
return value == null ? null : new EMailAddress(uri);
}
}
Upvotes: 3
Reputation: 48516
You can, with an implicit conversion:
public class EMailAddress
{
private string _address;
public EMailAddress(string address)
{
_address = address;
}
public static implicit operator EMailAddress(string address)
{
// While not technically a requirement; see below why this is done.
if (address == null)
return null;
return new EMailAddress(address);
}
}
Implicit conversions should only be used if no data is lost in the conversion. Even then, I recommend you use this feature sparingly, because it can make your code more difficult to read.
In this example, the implicit operator returns null
when a null
string is passed. As Jon Hanna correctly commented, it is undesirable to have these two snippets of code behave differently:
// Will assign null to the reference
EMailAddress x = null;
// Would create an EMailAddress object containing a null string
string s = null;
EMailAddress y = s;
Upvotes: 36
Reputation: 564891
You do this by adding an implicit operator from string to your custom type.
class EMailAddress
{
// ...other members
public static implicit operator EMailAddress (string address)
{
return new EMailAddress(address);
}
}
However, I recommend using this sparingly.
Upvotes: 9
Reputation: 10580
As you noted you can't inherit from string but you can extend it with extension methods.
So you can make some email specific extension methods to handle whatever it is you were thinking of putting in the EmailAddress class.
Upvotes: 0