为不可变类型层次结构定义通用初始值设定项

发布于 2024-11-02 15:04:17 字数 2095 浏览 1 评论 0原文

我想为一组“记录”类型定义一个接口和抽象基类。记录通常传递到另一个系统或从另一个系统传递,该系统将记录表示为字符串。因此,每个记录类都需要一个 Parse(string str) 方法和一个 ToString() 方法。记录类还需要定义比较相等性的方法,以及其他一些常见的实用方法。没有公共基础的示例记录类可能如下所示:

public class MyRecord : IEquatable<MyRecord>
{
    public string FieldA { get; private set; }
    public int FieldB { get; private set; }

    public MyRecord(string fieldA, int fieldB /* .. */) { }
    public static MyRecord Parse(string recordString) { /* .. */ }

    public override string ToString() { /* .. */  }
    public override int GetHashCode() { /* .. */ }
    public override bool Equals(object obj) { /* .. */ }
    public bool Equals(MyRecord other) { /* .. */ }
}

如果我可以将记录设计为不可变,它将简化使用场景。上面的类通过定义只读属性并在参数化构造函数或 Parse 方法中执行所有对象初始化来支持不变性。我特别没有公开默认构造函数。

我无法将此设计应用于具体记录类型可以继承的基记录类。具体来说,我需要一个通用的 Parse 方法,该方法可以实例化派生类型,而无需公开默认构造函数或部分构造的对象。到目前为止我的设计看起来像:

public interface IRecord { /* .. */ }

public abstract class RecordBase : IRecord
{
    public static TRecord Parse<TRecord>(string recordStr)
        where TRecord: RecordBase, new()
    {
        TRecord record = new TRecord();
        record.Initialize(recordStr);

        return record;
    }

    protected abstract void Initialize(string recordStr);
}

public class MyRecord : RecordBase, IEquatable<MyRecord>
{
    public string FieldA { get; private set; }
    public int FieldB { get; private set; }

    public MyRecord(string fieldA, int fieldB /* .. */) { /* .. */ }

    public override string ToString() { /* .. */  }
    public override int GetHashCode() { /* .. */ }
    public override bool Equals(object obj) { /* .. */ }
    public bool Equals(MyRecord other) { /* .. */ }

    protected MyRecord() { }
    protected override void Initialize(string recordStr) { /* .. */ }
}

但是,当我尝试调用 RecordBase.Parse(..) 时,编译器会抱怨,因为 MyRecord 的默认构造函数不是公开曝光。

所以我的问题是:

  • 是否有更好的设计可以让我拥有不可变的记录类型以及通用的 Parse 初始值设定项?或者尝试在基类级别使用通用初始化 API 创建不可变类型层次结构是否存在固有缺陷?

I'd like to define an interface and abstract base class for a set of "Record" types. Records are generally passed to/from another system that represents the record as a string. Thus, each record class will need a Parse(string str) method and a ToString() method. Record classes also need to define methods to compare for equality, as well as a few other common utility methods. A sample record class without a common base might look like:

public class MyRecord : IEquatable<MyRecord>
{
    public string FieldA { get; private set; }
    public int FieldB { get; private set; }

    public MyRecord(string fieldA, int fieldB /* .. */) { }
    public static MyRecord Parse(string recordString) { /* .. */ }

    public override string ToString() { /* .. */  }
    public override int GetHashCode() { /* .. */ }
    public override bool Equals(object obj) { /* .. */ }
    public bool Equals(MyRecord other) { /* .. */ }
}

It will simplify the usage scenarios if I can design the records as immutable. The above class supports immutability by defining read-only properties, and doing all object initialization in the parameterized constructor or Parse method. I have specifically not exposed the default constructor.

I'm having trouble applying this design to a base record class that concrete record types can inherit from. Specifically, I need a general Parse method that can instantiate the derived type, without exposing a default constructor or partially-constructed objects. The design I have so far looks like:

public interface IRecord { /* .. */ }

public abstract class RecordBase : IRecord
{
    public static TRecord Parse<TRecord>(string recordStr)
        where TRecord: RecordBase, new()
    {
        TRecord record = new TRecord();
        record.Initialize(recordStr);

        return record;
    }

    protected abstract void Initialize(string recordStr);
}

public class MyRecord : RecordBase, IEquatable<MyRecord>
{
    public string FieldA { get; private set; }
    public int FieldB { get; private set; }

    public MyRecord(string fieldA, int fieldB /* .. */) { /* .. */ }

    public override string ToString() { /* .. */  }
    public override int GetHashCode() { /* .. */ }
    public override bool Equals(object obj) { /* .. */ }
    public bool Equals(MyRecord other) { /* .. */ }

    protected MyRecord() { }
    protected override void Initialize(string recordStr) { /* .. */ }
}

However the compiler complains when I try to call RecordBase.Parse<MyRecord>(..) because MyRecord's default constructor is not exposed publicly.

So my question is:

  • Is there a better design that will allow me to have immutable record types as well as a common Parse initializer? Or is there an inherent flaw in trying to create an immutable type hierarchy with common initialization APIs at the base-class level?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

很糊涂小朋友 2024-11-09 15:04:17

为记录添加解析函数时会破坏 SRP。

  1. 创建一个单独的解析器类。
  2. 使用属性对其进行标记: [ParserFor(typeof(MyRecord))]
  3. 创建一个 ParserService 类。
  4. 使用反射扫描具有 ParserFor 属性的类型之后的所有加载的程序集。
  5. 使用解析器服务解析并创建所有记录。

You are breaking SRP when adding parsing functions for your records.

  1. Create a separate parser class.
  2. Tag it with an attribute: [ParserFor(typeof(MyRecord))]
  3. Create a ParserService class.
  4. Use reflection to scan all loaded assemblies after types that has the ParserFor attribute.
  5. Use the parser service to parse and create all records.
一场信仰旅途 2024-11-09 15:04:17

我认为您正在寻找工厂模式(从数据解析)和奇怪的重复模板模式(用于克隆/工厂能力)。

例如

abstract class Base<T> : IBase // IBase for common demoninator
{
    abstract T Parse(Stream data);
}

Derived : Base<Derived>
{

}

I think you are looking for a Factory Pattern (parse from data) and the Curiously Recurring Template Pattern (for the clone/factory ability).

E.g.

abstract class Base<T> : IBase // IBase for common demoninator
{
    abstract T Parse(Stream data);
}

Derived : Base<Derived>
{

}
灰色世界里的红玫瑰 2024-11-09 15:04:17

如果您保证非常小心,您也许可以使用 System.Runtime.Serialization.FormatterServices.GetUninitializedObject。但是,使用此方法需要保证所有 IRecord.Parse 实现都不会期望任何字段(根本)不为 null/0。

If you promise to be very careful, you might be able to use System.Runtime.Serialization.FormatterServices.GetUninitializedObject. However, this using this method will need the guarantee that none of the IRecord.Parse implementations expect any field (at all) to be not null/0.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文