C# Как правильно реализовать модель?

ВВЕДЕНИЕ

У меня есть класс Module. Он состоит, по-сути, только из одного поля - Symbol. Также он имеет перегруженные методы хеша и сравнения, где равными считаются модули с одинаковым Symbol.

public class Module : IEquatable<Module>
{
    public readonly char Symbol;

    public Module(char symbol)
    {
        Symbol = symbol;
    }

    /// <returns><see langword="true"/> if <paramref name="obj"/> is <see cref="Module"/> and modules are equal</returns>
    public override bool Equals(object obj) => Equals(obj as Module);

    /// <returns><see langword="true"/> if modules symbols are equal</returns>
    public bool Equals(Module module)
    {
        if (module is null)
            return false;

        if (ReferenceEquals(this, module))
            return true;

        if (GetType() != module.GetType())
            return false;

        return module.Symbol == Symbol;
    }

    public static bool operator ==(Module module1, Module module2)
    {
        if (module1 is null)
        {
            if (module2 is null)
                return true;
            else
                return false;
        }

        return module1.Equals(module2);
    }

    public static bool operator !=(Module module1, Module module2) => !(module1 == module2);

    public override int GetHashCode() => Symbol.GetHashCode();
}

Есть несколько наследников этого класса. Один из них - ParametricModule<T>. Он содержит в себе дополнительно параметр, и равенство двух таких модулей определяется, логично, по равенству Symbol и Parameter:

public class ParametricModule<T> : Module, ICloneable, IEquatable<ParametricModule<T>>
{
    public readonly T Parameter;

    public ParametricModule(char symbol, T parameter) : base(symbol)
    {
        Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
    }

    /// <returns><see langword="true"/> if <paramref name="obj"/> is <see cref="Module"/> and modules are equal</returns>
    public override bool Equals(object obj) => Equals(obj as ParametricModule<T>);

    /// <returns><see langword="true"/> if modules symbols and parameters are equal</returns>
    public bool Equals(ParametricModule<T> module)
    {
        if (module is null)
            return false;

        if (ReferenceEquals(this, module))
            return true;

        return module.Parameter.Equals(Parameter) && base.Equals(module);
    }

    public static bool operator ==(ParametricModule<T> module1, ParametricModule<T> module2)
    {
        if (module1 is null)
        {
            if (module2 is null)
                return true;
            else
                return false;
        }

        return module1.Equals(module2);
    }

    public static bool operator !=(ParametricModule<T> module1, ParametricModule<T> module2) => !(module1 == module2);

    public override int GetHashCode() => (Symbol, Parameter).GetHashCode();
}

ПРОБЛЕМА

Проблема состоит в том, что мне необходимо создать и передавать некоторый шаблон модуля. Шаблоны должны равнятся, если типы классов и Symbol-ы модулей совподают. Я хочу получить такой функционал:

var moduleA  = new Module('A');
var moduleA0 = new ParametricModule<int>('A', 0);
var moduleA1 = new ParametricModule<int>('A', 1);

moduleA != moduleA0 && moduleA != moduleA1 // РЕАЛИЗОВАНО: разные типы НЕ РАВНЫ
moduleA.Template != moduleA0.Template && moduleA.Template != moduleA1.Template // ХОЧЕТСЯ: шаблоны разных типов НЕ РАВНЫ

moduleA0 != moduleA1 // РЕАЛИЗОВАНО: одинаковые типы с одинаковой буквой, но с разными параметрами НЕ РАВНЫ
moduleA0.Template == moduleA1.Template // ХОЧЕТСЯ: шаблоны одинаковых типы с одинаковой буквой РАВНЫ

ВОПРОС

Как лучше всего реализовать подобный шаблон, его хранение и получение?

Моё виденье шаблона модуля очень похоже на то же, что если бы все наследники класса Module можно было привести к Module и использовать его родную функцию сравнения и получения хеша.


Ответы (1 шт):

Автор решения: Alexander Petrov

Создаём класс шаблона (GetHashCode нужно дописать):

public class Template : IEquatable<Template>
{
    private readonly Type Type;
    private readonly Module Module;

    public Template(Type type, Module module)
    {
        Type = type;
        Module = module;
    }

    public bool Equals(Template other)
    {
        if (this.Type != other.Type)
            return false;

        return this.Module.Equals(other.Module);
    }

    public override bool Equals(object obj)
    {
        if (obj is not Template other)
            return false;

        return this.Equals(other);
    }

    public static bool operator ==(Template template1, Template template2)
    {
        if (template1 is null)
        {
            if (template2 is null) return true;
            else return false;
        }
        return template1.Equals(template2);
    }

    public static bool operator !=(Template template1, Template template2)
        => !(template1 == template2);
}

Используем его в классах модулей:

public class Module : IEquatable<Module>
{
    public virtual Template Template { get; }
    ...
    public Module(char symbol)
    {
        Symbol = symbol;
        Template = new Template(typeof(Module), this);
    }
    ...

}
public class ParametricModule<T> : Module, IEquatable<ParametricModule<T>>
{
    public override Template Template { get; }
    ...
    public ParametricModule(char symbol, T parameter) : base(symbol)
    {
        Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
        Template = new Template(typeof(T), this);
    }
    ...
}

Вроде, работает как надо.


Как вариант, можно сделать класс шаблона вложенным в класс модуля. Это частично скроет его из видимости сторонних разработчиков.

→ Ссылка