11

我有两种不同类型的字符串在我的代码中传递和使用,两者密切相关,但不应相互混淆。我认为我可以通过拥有两个只是字符串但名称不同的类来帮助自己避免错误,以便方法签名(以及一般的类型不兼容)将强制执行两种不同类型字符串的语义含义。同时,我不想在一百个地方重构 from something = "foo";to 。something.Value = "foo";

第一个想法:

private class FirstKind : string { }
private class SecondKind : string { }

这个想法是,如果我有

void MyMethod(FirstKind varOne, SecondKind varTwo) {...}

,然后尝试用 调用它MyMethod(secondKindVar, firstKindVar);,我会得到一个编译器错误。

我撞到的墙: string被密封了。

第二个想法:创建泛型KindOf<T>类,它只会使用隐式转换运算符接收和吐出值。像这样:

private class KindOf<T>
{
    public T Value { get; set; }
    public KindOf() { Value = default(T); }
    public KindOf(T val) { Value = val; }
    public static implicit operator T(KindOf<T> kindOf) { return kindOf.Value; }
    public static implicit operator KindOf<T>(T value) { return new KindOf<T>(value); }
}

这样我可以做这样的事情:

private class FirstKind : KindOf<string> { }
private class SecondKind : KindOf<string> { }

我击中的墙:KindOf<T>该类似乎没有继承:派生类型中既不存在构造函数,也不存在运算符,这意味着我必须在派生类中重新实现整个基类。代码复制。呸。

所以我觉得我在这里错过了一些基本的东西,也许我已经离开了杂草。对我正在尝试做的事情有任何想法吗?

4

7 回答 7

3

老实说,我根本不会使用隐式运算符,也不会使用任何继承。整个想法是有一个方法,需要一个FirstKind. 使用隐式运算符,您只需传入一个字符串,它就会被转换;这意味着您不会强制代码明确声明它是第一种/第二种。我会坚持使用两个类,每个类只有一个构造函数,带有一个字符串和一个只读属性。通过添加其他任何内容,您已经过分复杂化了。

如果使用该属性变得烦人,我可能会从每个自定义类中看到隐式转换字符串,但我确实相信字符串的隐式转换将是有害的。虽然我同意通常要避免复制/粘贴代码,但当它实际上是一行代码时,复制粘贴隐式转换到两个类将比尝试使用继承或其他任何东西更容易、更有效且通常更易于维护避免写两次。

于 2012-06-21T14:58:53.817 回答
1

我将把我的评论作为一个答案,因为我认为它是一个答案。

在我的脑海中,您可能只需要在子类上实现隐式运算符和构造函数。是的,有点像代码复制,但至少当继承并没有真正传达任何真实的东西时,它没有使用复杂的过程或使用继承(从修辞上讲,无论如何,什么是“KindOf”,它真的意味着什么吗?)而且只是那里可以减少除了相似语法(但不同用途)之外没有任何共同点的类之间的代码重复

public class FirstKind
{
    public string Value { get; private set; }

    public FirstKind(string value)
    {
        this.Value = value;
    }

    public static implicit operator FirstKind(string value)
    {
        return new FirstKind(value);
    }

    public static implicit operator string(FirstKind value)
    {
        return value.Value;
    }
}

此外,如果它们应该表示字符串,您可能需要考虑使类不可变。(另一个可能指向这些对象的实现细节可能不应该从重用KindOf类继承,在您的实现中,这使得它们总是可变的)

它没有太多的布线,并且让您的代码可以在未来进行更改(也许您希望添加一个新属性?添加一些验证代码?)并且 API 的使用者真的不在乎任何东西KindOf只是为了节省一些问题代码重复。(也许你可以制作一个片段让你的生活更轻松一些?)

于 2012-06-21T14:59:25.800 回答
1

在您的问题中,FirstKindandSecondKind是您的类字符串变量的类型,并且string是您的KindOf<T>类的类型参数。另一种方法(避免其他答案所需的代码重复)是翻转 and 的角色FirstKindSecondKind使它们成为空的“标记”类,用作类型的类型参数String<TKind>

public class FirstKind { }
public class SecondKind { }

public struct String<TKind>
{
    private readonly string _value;

    public String(string value)
    {
        _value = value;
    }

    public override string ToString()
    {
        return _value;
    }

    public static implicit operator String<TKind>(string s) // see the note below
    {
        return new String<TKind>(s);
    }

    public static implicit operator string(String<TKind> s)
    {
        return s.ToString();
    }

    public static String<TKind> operator +(String<TKind> s1, String<TKind> s2)
    {
        return new String<TKind>(s1.ToString() + s2.ToString());
    }

    public int Length
    {
        get { return _value.Length; }
    }

    // You can add methods like Substring and Trim that delegate to _value.
}

现在你可以MyMethod这样声明:

void MyMethod(String<FirstKind> varOne, String<SecondKind> varTwo)
{
    varOne += varOne; // OK
    varTwo += varTwo; // OK
    varOne = varTwo;  // ERROR
}

注意:我已经从普通的string. 您可能需要考虑删除它,强制代码显式识别字符串的类型:new String<FirstKind>("...").

于 2012-06-21T15:04:22.827 回答
0

I would just do this for strings.

abstract class MyTypedString
{
   protected MyTypedString(string value)
   {
     Value = value;
   }
   public string Value { get; private set; }
}

Then you could finish off this abstract class with your FirstKind and SecondKind. This will keep the object immutable like how strings are. I would not give this any implicit conversion operators.

于 2012-06-21T14:50:43.247 回答
0

运算符仍然可以工作,您只需要引用.ValueKindOf 类的成员:

FirstKind stringFoo = new FirstKind("foo");
FirstKind stringQux = new FirstKind("qux");

// String concatenation operator
FirstKind stringTar = new FirstKind( stringFoo.Value + stringQux.Value );

但是,正如您所说,如果将两者混合使用,则不会有“类型检查”,例如

SecondKind stringBar = new SecondKind("bar");
FirstKind stringHal = new FirstKind( stringFoo.Value + stringBar.Value ); // this will succeed, even though you intend stringFoo and stringBar to be incompatible

由于运算符是类接口的一部分,因此您必须重新实现它们 - 但是您可以只包装包含的类的实现,因此您只需要做一些无聊的工作即可使其正常工作。

于 2012-06-21T14:53:53.223 回答
0

您可以尝试将类上的隐式转换运算符重载为字符串。这样您就不必更新对 .Value 的引用。这些类必须保持独立,您必须为它们创建接受字符串值的构造函数。最佳做法是保留字符串readonly,以便类变得不可变。模仿它所代表的字符串类。

public class FirstKind 
{
      private readonly string value;
      public FirstKind(string v) { this.value = v };
      public static implicit operator string(FirstKind v){ return v.value; }
}

public class SecondKind 
{
      private readonly string value;
      public SecondKind (string v) { this.value = v };
      public static implicit operator string(SecondKind v){ return v.value; }
}
于 2012-06-21T14:54:32.210 回答
0

你几乎可以使用你的泛型类。只有从字符串(或其他类型)到派生类的隐式转换永远不会起作用,因为基类不知道字符串应该转换为第一类还是第二类。因此应该复制该部分,但其余部分可以保留在基类中:

private class FirstKind : KindOf<string>
{
    public static implicit operator FirstKind(string value) { return new FirstKind{Value = value}; }
}
private class SecondKind : KindOf<string>
{
    public static implicit operator SecondKind(string value) { return new SecondKind{Value = value}; }
}

仍然可以从泛型类中转换到基类:

        FirstKind Foo = "Foo"; //via implicit operator in FirstKind
        string stringFoo = Foo; //via implicit operator in baseclass
于 2012-06-21T14:55:27.560 回答