2010-08-08 4 views
18

Я хочу создать свой собственный класс EMailAddress, который действует как строковый класс.Создать собственный класс строк

Так что я хотел бы сделать это

private EMailAddress _emailAddress = "[email protected]"; 

вместо

private EMailAddress _emailAddress = new EMailAddress("[email protected]"); 

Есть ли способ сделать то, что я хочу, или мне нужно использовать второй вариант. Поскольку строка запечатана, я не могу ее использовать, а оператор = не может быть перегружен, поэтому я не в курсе, как это исправить ....

+6

Если это похоже на строку - это должна быть строка. Я не уверен, что это возможно и даже хорошая идея. – zerkms

+0

Является ли класс строки дефектным? –

+2

Никто не ожидал увидеть подобное. Просто создайте свой собственный класс. –

ответ

28

Вы можете, с implicit преобразования:

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; 
+3

У этого есть 'EmailAddress x = null;' ведет себя по-другому: 'String s = null; EmailAddress x = s;' что странно , Лучше иметь неявный оператор, возвращающий null по нулевому параметру. –

+0

Неявные конверсии, похоже, побеждают всю идею наличия отдельного класса. –

+0

@ Антон, почему? Считаете ли вы тот факт, что мы можем неявно использовать int для двойного поражения, имея в виду их отдельно? –

0

Как вы отметили, вы не можете наследовать строку, но вы может расширить его с помощью методов расширения.

Таким образом, вы можете использовать некоторые методы расширения электронной почты для обработки того, что вы думаете о том, чтобы поместить класс EmailAddress.

+0

Как было бы связано с заданием с помощью метода расширения? –

+0

Не задание. Я предположил, что есть причина для создания класса, отличного от простого назначения. – Sruly

+0

Лично я бы предпочел создать свой собственный класс EMailAddress, вместо того, чтобы добавлять нежелательный файл адреса электронной почты в 'System.String' с помощью методов расширения. Существует, однако, довольно приличный класс «MailAddress» :) – Thorarin

8

Вы делаете это, добавляя implicit operator from string к своему таможнному типу.

class EMailAddress 
{ 
     // ...other members 

     public static implicit operator EMailAddress (string address) 
     { 
      return new EMailAddress(address); 
     } 
} 

Однако я рекомендую использовать это экономно.

+0

, что очень круто. Я думал, что-то подобное существует. Я должен попытаться больше не забывать об этом! EDIT: ах, я не поймал ваше редактирование. почему вы рекомендуете использовать его экономно? Думаю, я всегда мог закончить чтение страницы msdn ... –

+0

Справедливо = это нарушает принцип наименьшего удивления. Программисты этого не ожидали, поэтому сделать код сложным и вести себя неожиданно. НО, класс string IS неисправен, а строка нарушает POLA, являясь ссылочным типом, но действуя как тип значения. Многие из моего абстрактного обобщенного класса требуют конструкторов без параметров, поэтому я не могу использовать класс String в своих дженериках. Таким образом, я создал класс StringMutable, чтобы его можно было использовать в моих абстрактных классах, где: new() –

0

Я думаю, что я вижу, что вы пытаетесь сделать. Но вы не можете подкласса string в .NET. Иногда мне хотелось, чтобы я мог подклассировать встроенные типы, такие как int, но, конечно, это невозможно (хотя и по другим техническим причинам).

Хотя теоретически вы можете использовать метод преобразования implicit operator, как предложено другими здесь, есть некоторые проблемы с такими, как странные ошибки компилятора типа. Это решение не очень хорошо работает для меня.

То, что вы обычно видите в BCL в подобных сценариях — т.е. когда специализированный тип должен «чувствовать себя» как тип он основан на — является пользовательским типом, имеющим Value свойства. (Два примера, которые приходят на ум, - это System.Nullable<T>, System.Lazy<T>.) Это не так элегантно, но оно работает.

2
  1. Неявный оператор должен вводить null в другое значение null, за исключением случаев, когда тип cast to не имеет значения NULL, и в этом случае он должен ошибаться при нуле.
  2. Пожалуйста, если вы пишете что-то, что содержит 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); 
    } 
} 
+1

Добавление проверки адреса электронной почты в класс имеет смысл , Тем не менее, * неявные * преобразования, которые могут терпеть неудачу, - это немного запаха кода, imo. – Thorarin

+0

Кроме того, я немного разочарован тем, что вы не реализовали полную RFC822 (или RFC5322, которая ее заменяет);) – Thorarin

+0

Ну, полная проверка просто означала бы добавление действительно длинного Regex в пример, и часто это не так В любом случае, это действительно нужно. Что касается плохого запаха кода, то запахи кода относительно. Это подтолкнет меня к тому, чтобы разрешить неявное (и если бы я сам создавал этот класс, вероятно, опроверг бы меня против того, что я этого не сделаю), но я не увижу его как определенного нет-нет. –

1

Я думаю, что этот вопрос является частным случаем более общего вопроса о создании типобезопасность через C# приложение. Мой пример здесь состоит из двух типов данных: Цены и веса. У них разные единицы измерения, поэтому никогда не следует пытаться назначить цену весу или наоборот. Оба под крышками - это действительно десятичные значения. (Я игнорирую тот факт, что могут быть конверсии, например, килограммы и т. Д.). Эта же идея может быть применена к строкам с определенными типами, такими как EmailAddress и UserLastName.

С некоторым кодом котельной плиты можно сделать либо явное преобразование, либо неявное преобразование между конкретными типами: цена и вес, а также базовый тип Десятичный.

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 
    } 

Просто изменение «явные» операторы «неявные» операторы, заменив слова «явно» с «неявным» в коде, можно преобразовать назад и вперед к десятичному классу без какого-либо Дополнительная работа. Это приводит к тому, что 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 
    }  

При выполнении этого для строки вместо десятичного. Мне нравится идея ответа Торарина о проверке 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, оператор ==, а оператор! =

Чтобы сделать все это проще, я сделал класс ValueType, который я могу расширить, класс ValueType вызывает базовый тип для всех, кроме операторов преобразования.

Смежные вопросы