【问题标题】:How can I declare derived "shell" classes that do nothing but act as renames?我怎样才能声明派生的“shell”类除了作为重命名之外什么都不做?
【发布时间】:2012-06-21 14:46:12
【问题描述】:

我在代码中传递和使用了两种不同类型的字符串,两者密切相关,但不应相互混淆。我认为我可以通过拥有两个只是字符串但名称不同的类来帮助自己避免错误,以便方法签名(以及一般的类型不兼容)将强制执行两种不同类型字符串的语义含义。同时,我不想在一百个地方从something = "foo";重构为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&lt;T&gt; 类似乎没有继承:派生类型中既不存在构造函数也不存在运算符,这意味着我必须在派生类中重新实现整个基类。代码复制。呸。

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

【问题讨论】:

  • 也许你能澄清一下“两种不同的字符串”是什么意思?
  • 在我的脑海中,您可能只需要在子类上实现隐式运算符和构造函数。是的,有点像代码复制,但至少它没有采用复杂的过程或在继承没有真正传达任何真实的情况下使用继承(从修辞上讲,无论如何,什么是“KindOf”并且确实如此意味着什么?)并且只是为了减少除了相似语法(但不同用途)之外没有任何共同点的类之间的代码重复
  • 同意,这些类真的不应该在同一个继承层次结构中,特别是考虑到你不想想要用一个通用类型来引用它们,你想要它们保持独特。
  • @BryanCrosby 想象一下,对于字符串代码而不是整数或 Guid 的记录,您有唯一的 ID。并且您要确保不会意外地将 PersonId 字符串传递到需要 CompanyId 字符串的方法中。对我来说,那时这样的东西肯定很方便。

标签: c# .net generics inheritance defensive-programming


【解决方案1】:

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

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

【讨论】:

  • +1 不幸的是,由于String 是密封的,恐怕这可能是最好的选择。
  • 我们同意这一点。 “隐式转换为字符串”是我的想法,但仍然按照您的建议保持类分开。
【解决方案2】:

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

在我的脑海中,您可能只需要在子类上实现隐式运算符和构造函数。是的,有点像代码复制,但至少当继承并没有真正传达任何真实的东西时,它没有使用复杂的过程或使用继承(从修辞上讲,无论如何,什么是“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只是为了节省一些次要代码重复。 (也许你可以制作一个 sn-p 让你的生活更轻松一些?)

【讨论】:

    【解决方案3】:

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

    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&lt;FirstKind&gt;("...")

    【讨论】:

    • 嗯,有趣的方法。我得考虑一下。
    【解决方案4】:

    我只会对字符串这样做。

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

    然后你可以用你的FirstKindSecondKind 完成这个抽象类。这将保持对象不可变,就像字符串一样。我不会给这个任何隐式转换运算符。

    【讨论】:

    • 这与他的第二个示例基本相同(只是不太好)。
    【解决方案5】:

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

    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
    

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

    【讨论】:

      【解决方案6】:

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

      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
      

      【讨论】:

      • 仍然存在的问题是你不能做像Foo.Length 这样的事情。您必须修复所有代码才能执行 Foo.Value.Length 之类的操作,这可能不值得这么麻烦。
      • 是的,这反过来会调用继承自 KindOf 并实现其他类型继承的属性的中间 KindOfString 类(或者如果仅使用字符串,则跳过泛型一起上课)。
      【解决方案7】:

      您可以尝试将类上的implicit 转换运算符重载为字符串。这样您就不必更新对 .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; }
      }
      

      【讨论】:

      • 你应该使用一个属性而不是暴露一个字符串字段,它应该是只读的,而不是可变的。
      • 我不打算让它暴露在外。只是展示这个概念。我会将它保持为私有和只读,然后只能通过隐式转换访问它。从长远来看,这将使代码更具可读性。
      猜你喜欢
      • 2022-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-22
      • 1970-01-01
      • 1970-01-01
      • 2014-08-26
      相关资源
      最近更新 更多