【问题标题】:Concrete Class inherit from Abstract class which inherits from Generic Abstract Class具体类继承自抽象类,抽象类继承自通用抽象类
【发布时间】:2017-02-07 19:37:48
【问题描述】:

我是这里的“老派”程序员,正在努力利用继承来发挥自己的优势。我发现自己在重复代码,它开始有异味。我没有坚持 DRY,所以我在这里尝试重构一下以减少代码重复!

我正在尝试编写要在我的实体中使用的值对象类,这将强制执行基本不变量。我有一个通用的抽象 ValueObject 类,可以像这样处理相等和哈希:

public abstract class ValueObject<T> where T : ValueObject<T>
{
    protected abstract IEnumerable<object> GetEqualityCheckAttributes();

    public override bool Equals(object other)
    {
        return Equals(other as T);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }
        return GetEqualityCheckAttributes().SequenceEqual(other.GetEqualityCheckAttributes());
    }

    public static bool operator == (ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator != (ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetEqualityCheckAttributes())
        {
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());
        }
        return hash;
    }
}

然后我一直在创建我的值对象类,然后实现这个抽象类,并提供逻辑以确保不能在无效状态下创建对象。这是我开始违反 DRY 的时候,并且正在使用相同的代码创建许多对象(例如,最大长度为 50 或 30 或 10 的必需字符串)。

所以我希望将执行不变量的代码放在它自己的类中,并让我的具体值对象类继承该功能。类似的东西(这不会编译,见下文):

public abstract class RequiredStringValueObject : ValueObject<string>
{
    private string _value;
    protected string _fieldName;
    protected byte _maxLength;

    public string Value
    {
        get
        {
            return _value;
        }
        protected set
        {
            if (value == null || string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentNullException(_fieldName, _fieldName + " must be supplied.");
            }
            value = value.Trim();
            if (value.Length > _maxLength)
            {
                throw new ArgumentOutOfRangeException(_fieldName, value, _fieldName + " can't be longer than " + _maxLength.ToString() + " characters.");
            }
            _value = value;
        }
    }
}

然后我可以像这样在具体类中“使用”所有这些功能:

public class FirstName : RequiredStringValueObject
{
    private FirstName(string value, string FieldName, byte MaxLength)
    {
        _fieldName = FieldName;
        _maxLength = MaxLength;
        Value = value;
    }
    public static FirstName Create(string value, string FieldName, byte MaxLength)
    {
        return new FirstName(value, FieldName, MaxLength);
    }

    protected override IEnumerable<object> GetEqualityCheckAttributes()
    {
        return new List<object> { Value };
    }
}

所有这些似乎都是解决问题的合理方法(对我而言)。问题是我在RequiredStringValueObject 声明中遇到编译器错误:

类型string 不能用作泛型类型或方法ValueObject&lt;T&gt; 中的类型参数T。没有从stringValueObject&lt;string&gt; 的隐式引用转换。

我不完全理解错误消息。我正在尝试做的事情可能吗?有没有办法使这项工作?或者我可以/应该采取其他方法吗?

【问题讨论】:

  • 我不确定,但为什么你有where T : ValueObject&lt;T&gt;
  • 错误是由于类是如何定义的。你有public abstract class ValueObject&lt;T&gt; where T : ValueObject&lt;T&gt;。本质上你是在说——我声明了一个泛型类,其中 T 被限制在这个类的实例中。循环定义的排序。 String 不满足该标准。

标签: c# generics abstract-class c#-6.0


【解决方案1】:

你的泛型类型 T 上有一个 where 子句:

public abstract class ValueObject<T> where T : ValueObject<T>

这告诉编译器 T 必须从 ValueObject 派生,而 string 不是。

你想用 where T: 子句来强制执行什么?你可能想省略它。

【讨论】:

    【解决方案2】:

    问题出在这行:

    abstract class ValueObject<T> where T : ValueObject<T>
    

    您要求T 继承自ValueObject&lt;T&gt;,所以当您编写时:

    RequiredStringValueObject : ValueObject<string>
    

    string 不从ValueObject 继承(显然)所以你需要从ValueObject&lt;ValueObject&lt;string&gt;&gt; 继承,除了 违反了约束,嗯......它的海龟一路下来。

    简单的解决方法是删除类型约束;似乎您的代码主要是为处理object 而设置的,因此您不需要它。放置任何类型的“递归”类型约束只会导致您在此设置中出现问题。如果你真的需要这样的东西,你可能需要改用组合,比如:

    public interface IValueMethods<T>
    {
       //required methods
    }
    
    //Constructor for value object
    public ValueObject<T>(IValueMethods<T> commonMethods)
    {
    }
    

    然后你可以传入这组方法作为一个单独的对象使用。

    【讨论】:

    • 移除约束然后将问题渗透到我的 ValueObject 抽象类中。在 Equals 方法中,我得到“类型参数 'T' 不能与 'as' 运算符一起使用,因为它既没有类类型约束也没有 'class' 约束。”所以我从“other as T”变成了“other as object”?
    • 另外,other.GetEqualityCheckAttributes() 然后有问题:“'T' 不包含'GetEqualityCheckAttributes' 的定义,并且没有扩展方法'GetEqualityCheckAtributes' 接受'T' 类型的第一个参数可能是找到”
    • @Scuzzlebutt 你可能不得不重新设计GetEqualityCheckAttributes,也许是上面的界面建议。至于第一个,您可以设置 where T : class 约束,但我严重怀疑您想要,因为您似乎希望它与 int、short 等一起使用。
    • 也许将T 限制为IEquatable?看起来您只对其执行“相等”类型的操作。该过滤器将允许您通过该接口实现比较。
    • 是的,看来我需要稍微修改一下我的 ValueObject 类。相等性检查现在无法正常工作。
    【解决方案3】:

    与@BradleyDotNET 所说的一致。一个可能的修复可能如下所示:

    public abstract class ValueObjectBase
    {
        public abstract IEnumerable<object> GetEqualityCheckAttributes();
    }
    
    public abstract class ValueObject<T> : ValueObjectBase where T : class
    {
        public override bool Equals(object other)
        {
            if (other is ValueObjectBase)
                return Equals(other as ValueObjectBase);
    
            return Equals(other as T);
        }
    
        public bool Equals(T other)
        {
    
            if (other == null)
            {
                return false;
            }
            return other.Equals(this);
    
        }
    
        public bool Equals(ValueObjectBase other)
        {
            return GetEqualityCheckAttributes().SequenceEqual(other.GetEqualityCheckAttributes());
        }
    
        public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
        {
            return Equals(left, right);
        }
    
        public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
        {
            return !(left == right);
        }
    
        public override int GetHashCode()
        {
            int hash = 17;
            foreach (var obj in this.GetEqualityCheckAttributes())
            {
                hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());
            }
            return hash;
        }
    }
    

    【讨论】:

    • 感谢您的建议!我自己绝对不会想到这一点。
    【解决方案4】:

    感谢您的所有帮助,这是最终的工作解决方案:

    值对象

    public abstract class ValueObjectBase
    {
        public abstract IEnumerable<object> GetEqualityCheckAttributes();
    }
    
    public abstract class ValueObject<T> : ValueObjectBase
    {
        public override bool Equals(object other)
        {
            if (other is ValueObjectBase)
            {
                return Equals(other as ValueObjectBase);
            }
            return Equals(other as IEquatable<T>);
        }
    
        public bool Equals(T other)
        {
            if (other == null)
            {
                return false;
            }
            return other.Equals(this);
        }
    
        public bool Equals(ValueObjectBase other)
        {
            return GetEqualityCheckAttributes().SequenceEqual(other.GetEqualityCheckAttributes());
        }
    
        public static bool operator == (ValueObject<T> left, ValueObject<T> right)
        {
            return Equals(left, right);
        }
    
        public static bool operator != (ValueObject<T> left, ValueObject<T> right)
        {
            return !(Equals(left, right));
        }
    
        public override int GetHashCode()
        {
            int hash = 17;
            foreach (var obj in this.GetEqualityCheckAttributes())
            {
                hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());
            }
            return hash;
        }
    }
    

    主题的变体,RequiredStringValueObject

    public abstract class RequiredStringValueObject : ValueObject<string>
    {
        private string _value;
        protected string _fieldName;
        protected byte _maxLength;
    
        public string Value
        {
            get
            {
                return _value;
            }
            protected set
            {
                if (value == null || string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentNullException(_fieldName, _fieldName + " must be supplied.");
                }
                value = value.Trim();
                if (value.Length > _maxLength)
                {
                    throw new ArgumentOutOfRangeException(_fieldName, value, _fieldName + " can't be longer than " + _maxLength.ToString() + " characters.");
                }
                _value = value;
            }
        }
    
        protected RequiredStringValueObject(string fieldName, byte maxLength, string value)
        {
            _fieldName = fieldName;
            _maxLength = maxLength;
            Value = value;
        }
    
        public override IEnumerable<object> GetEqualityCheckAttributes()
        {
            return new List<object> { Value };
        }
    }
    

    具体的实现,FirstName(一个必需的基于字符串的最大长度的值对象):

     public class FirstName : RequiredStringValueObject
    {
        private FirstName(string value) : base(nameof(FirstName),30, value) { }
    
        public static FirstName Create(string value)
        {
            return new FirstName(value);
        }
    
    }
    

    正如 80 后的孩子所说,“完全管状!”

    谢谢!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-07
      • 1970-01-01
      • 2021-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多