创建自定义字符串类

时间:2010-08-08 21:16:19

标签: c#

我想创建自己的EMailAddress类,其作用类似于字符串类。

所以我喜欢这样做

private EMailAddress _emailAddress = "Test@Test.com";

而不是

private EMailAddress _emailAddress = new EMailAddress("Test@Test.com");

有没有办法实现我想要的,或者我需要使用第二种选择。由于字符串是密封的,我不能使用它,并且=运算符不能重载,所以我没有想法如何解决这个....

7 个答案:

答案 0 :(得分:29)

您可以通过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。正如Jon Hanna正确评论的那样,让这两段代码表现不同是不可取的:

// Will assign null to the reference
EMailAddress x = null;

// Would create an EMailAddress object containing a null string
string s = null;
EMailAddress y = s;

答案 1 :(得分:8)

您可以通过向自定义类型添加implicit operator from string来执行此操作。

class EMailAddress 
{
        // ...other members

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

但是,我建议谨慎使用。

答案 2 :(得分:4)

我认为这个问题是关于在C#应用程序中创建类型安全性的更一般问题的特定情况。我的例子是两种类型的数据:价格和权重。它们具有不同的度量单位,因此不应该尝试为权重分配价格,反之亦然。两个封面下都是十进制值。 (我忽略了这样一个事实,即可以转换成磅到kg等。)同样的想法可以应用于具有特定类型的字符串,如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
    }

只需更改"显式"运营商"隐含"运营商通过替换单词" explicit"用"含义"在代码中,可以在没有任何额外工作的情况下来回转换到底层的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而不是Decimal执行此操作时。我喜欢Thorarin关于检查null并在转换中传回null的回答。 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);
}

要使这些类作为Dictionary集合的键,您还需要实现Equals,GetHashCode,operator ==和operator!=

为了使所有这些更容易,我创建了一个可以扩展的ValueType类,ValueType类调用除转换运算符之外的所有内容的基类型。

答案 3 :(得分:3)

  1. 隐式运算符应该将null转换为另一个null,除非类型转换为不可为空,在这种情况下它应该在null上出错。
  2. 如果您正在撰写包含Uri的内容,请不要强迫使用Uri的人使用Uri来自行获取字符串。电子邮件地址自然适合mailto:uris,所以虽然这不是一个很好的例子,但它很接近。
  3. 示例:

    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 :(得分:0)

正如您所指出的,您无法从字符串继承,但可以使用扩展方法对其进行扩展。

因此,您可以制作一些特定于电子邮件的扩展方法来处理您想要放入EmailAddress类的任何内容。

答案 5 :(得分:0)

我想我知道你要做什么。但是你不能在.NET中继承string。有时我希望我可以继承int这样的内置类型,但当然这也是不可能的(尽管出于其他技术原因)。

虽然你理论上可以使用implicit operator转换方法,正如其他人所建议的那样,但是存在一些问题,例如奇怪的类型转换编译器错误。这个解决方案对我来说效果不佳。

在类似情况下您通常会在BCL中看到的内容 - 即,当某个特定类型应该“感觉”类似于它所基于的类型时 - 是具有 Value属性的自定义类型。 (我想到的两个例子是System.Nullable<T>System.Lazy<T>。)它并不优雅,但却有效。

答案 6 :(得分:0)

作为@Thorarin 代码的后续:

您可以将其用作基本代码并添加普通字符串类将具有的额外功能:

public class VarChar
{
        private string _content;

        public VarChar(string argContent)
        {
            _content = argContent;
        }

        public static implicit operator VarChar(string argContent)
        {
            // While not technically a requirement; see below why this is done.
            if (argContent == null)
                return null;

            return new VarChar(argContent);
        }

        public static implicit operator string(VarChar x) => x._content;

        public override string ToString()
        {
            return _content;
        }
}

所以下面的代码就行了:

VarChar tempVarChar = "test";

string tempTest = tempVarChar; // = "test"

var tempText = tempVarChar.ToString();

tempVarChar = tempVarChar + "_oke";

tempText = tempVarChar; // = "test_oke"