Создать собственный класс строк
Я хочу создать свой собственный класс EMailAddress, который будет действовать как строковый класс.
Итак, мне нравится делать это
private EMailAddress _emailAddress = "[email protected]";
вместо
private EMailAddress _emailAddress = new EMailAddress("[email protected]");
Есть ли способ выполнить то, что я хочу, или мне нужно использовать вторую альтернативу. Поскольку строка запечатана, я не могу ее использовать, и оператор = не может быть перегружен, поэтому я не понимаю, как это исправить....
Ответы
Ответ 1
Вы можете с неявным преобразованием:
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);
}
}
Неявные преобразования следует использовать, только если данные не теряются при преобразовании. Даже тогда я рекомендую использовать эту функцию экономно, потому что это может сделать ваш код более трудным для чтения.
В этом примере неявный оператор возвращает null
, когда передается строка null
. Как правильно прокомментировал Джон Ханна, нежелательно, чтобы эти два фрагмента кода ведут себя по-другому:
// Will assign null to the reference
EMailAddress x = null;
// Would create an EMailAddress object containing a null string
string s = null;
EMailAddress y = s;
Ответ 2
Вы делаете это, добавляя неявный оператор из строки в свой собственный тип.
class EMailAddress
{
// ...other members
public static implicit operator EMailAddress (string address)
{
return new EMailAddress(address);
}
}
Однако я рекомендую использовать это экономно.
Ответ 3
- Неявный оператор должен отбрасывать null в другой null, за исключением случаев, когда тип cast to не имеет значения NULL, и в этом случае он должен ошибаться в null.
- Пожалуйста, если вы пишете что-то, что содержит Uri, не заставляйте людей использовать его, у которых есть Uri, чтобы выполнить работу по получению строки для нее. Адреса электронной почты подходят, естественно, к mailto: uris, поэтому, хотя это не совсем пример этого, он закрывается.
Пример:
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);
}
}
Ответ 4
Я думаю, что этот вопрос является конкретным случаем более общего вопроса о создании безопасности типов в приложении С#. Мой пример здесь состоит из двух типов данных: Цены и веса. У них разные единицы измерения, поэтому никогда не следует пытаться назначить цену весу или наоборот. Оба под крышками - это действительно десятичные значения. (Я игнорирую тот факт, что могут быть преобразования, например, килограммы и т.д.). Эта же идея может быть применена к строкам с определенными типами, такими как EmailAddress и UserLastName.
С помощью некоторого довольно кодового табличного кода можно сделать либо явное преобразование, либо неявное преобразование между конкретными типами: цена и вес, а также базовый тип 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;
}
};
При переопределении "явного" оператора получается более ограничительный набор вещей, который можно сделать с этими классами. Каждый раз, когда вы переходите от одного типа к другому, вы должны быть вручную. Например:
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
}
Просто меняя "явные" операторы на "неявные" операторы, заменив слова "явным" на "неявные" в коде, можно преобразовать назад и вперед в базовый класс Decimal без какой-либо дополнительной работы. Это приводит к тому, что Price и Weight ведут себя как Decimal, но вы по-прежнему не можете изменить цену на вес. Обычно это уровень безопасности типов, который я ищу.
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
}
При выполнении этого для String вместо десятичного. Мне нравится идея Thorarin ответить о проверке на null и передачи null обратно в преобразовании. например.
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);
}
Чтобы эти классы работали как ключи к коллекциям словаря, вам также необходимо реализовать Equals, GetHashCode, operator == и operator!=
Чтобы сделать все это проще, я создал класс ValueType, который я могу расширить. Класс ValueType вызывает базовый тип для всех, кроме операторов преобразования.
Ответ 5
Как вы заметили, вы не можете наследовать строку, но можете расширить ее с помощью методов расширения.
Таким образом, вы можете использовать некоторые методы расширения электронной почты для обработки того, что вы думаете о том, чтобы поместить класс EmailAddress.
Ответ 6
Думаю, я вижу, что вы пытаетесь сделать. Но вы не можете подклассифицировать string
в .NET. Иногда мне хотелось, чтобы я мог подклассифицировать встроенные типы, такие как int
, но, конечно, это невозможно (хотя и по другим техническим причинам).
В то время как теоретически можно использовать метод преобразования implicit operator
, как предложено другими здесь, есть некоторые проблемы с такими, как странные ошибки компилятора типа. Это решение не очень хорошо работает для меня.
Что вы обычно видите в BCL в похожих сценариях -— т.е. когда специализированный тип должен "чувствовать", как тип, основанный на — это нестандартный тип, имеющий свойство Value
. (Два примера, которые приходят на ум, - это System.Nullable<T>
, System.Lazy<T>
.) Это не так элегантно, но оно работает.