Подтвердить что ты не робот

Создать собственный класс строк

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

Итак, мне нравится делать это

private EMailAddress _emailAddress = "[email protected]";

вместо

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

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

4b9b3361

Ответ 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>.) Это не так элегантно, но оно работает.