C# 中的重载构造函数类似于 Delphi(具有多个名称)

本文关键字:重载 构造函数 Delphi 类似于 | 更新日期: 2023-09-27 18:36:41

我正在尝试将一些代码从Delphi移植到C#,我发现了一个结构,我无法以合理的方式实现,同时遵守.NET Framework设计指南(我在问题末尾解决了这个问题)。

显然,C#,Java,C++(以及许多其他语言)提供了方法/构造函数重载的含义,但Delphi构造函数还可以有多个名称。通过这种方式,可以编写直接表示意图的代码:

var
  Data, ParI, ParD, Locl: TDataElement;
begin
  Data := TDataElement.Create('Element');
  ParI := TDataElement.CreateParam('IntElement', 22);
  ParD := TDataElement.CreateParam('DblElement', 3.14);
  Locl := TDataElement.CreateLocal('LocalElement');
  // ... use the above objects ...
end;

简化代码如下:

unit DataManager;
interface
TDataElement = class
  FName: string;
  FPersistent: Boolean;
public
  constructor Create(AName: string);
  constructor CreateParam(AName: string; DefaultInt: Integer); overload;
  constructor CreateParam(AName: string; DefaultDouble: Double); overload;
  constructor CreateLocal(AName: string);
  property Name: string read FName;;
  property Persistent: Boolean read FPersistent;
end;
implementation
constructor TDataElement.Create(AName: string);
begin
  FName := AName;
  FPersistent := True;
  // ... other initialization ...
end;
constructor TDataElement.CreateParam(AName: string; DefaultDouble: Double);
begin
  Create(AName); 
  // ... use DefaultInt ...
end;
constructor TDataElement.CreateParam(AName: string; DefaultInt: Integer);
begin
  Create(AName); 
  // ... use DefaultDouble...
end;
constructor TDataElement.CreateLocal(AName: string);
begin
  Create(AName); 
  FPersistent := False;
  // ... other code for local (non-persistent) elements ...
end;

特别是在 C# 中,构造函数必须与类具有相同的名称,因此首先我尝试使用枚举来区分行为。唉,我偶然发现了几个问题:

  • 每个构造函数中的第一个参数是相同的类型 (ElementKind)
  • 构造函数不像在Delphi中那样容易识别(Create vs. CreateParam vs. CreateLocal)
  • 在 DataElement 的子类中需要格外小心
  • 错误的可能性,例如指定 ElementKind.DoubleParam 和传递整数值
  • 需要额外的 bool 参数来处理局部元素

第一次尝试如下:

public enum ElementKind
{
    Regular, IntParam, DoubleParam, Local
}
public class DataElement
{
    private string FName;
    public string Name { get { return FName; } }
    private bool FPersistent;
    public bool Persistent { get { return FPersistent; } }
    public DataElement(ElementKind kind, string name)
    {
        FName = name;
        // ugly switch :-(
        switch (kind)
        {
            case ElementKind.Regular:
            case ElementKind.IntParam:
            case ElementKind.DoubleParam:
                FPersistent = true;
                break;
            case ElementKind.Local:
                FPersistent = false;
                break;
        }
        // ... other initialization ...
    }
    public DataElement(ElementKind kind, string name, int defaultInt)
        : this(kind, name)
    {
        // ... use defaultInt ...
    }
    public DataElement(ElementKind kind, string name, double defaultDouble)
        : this(kind, name)
    {
        // ... use defaultDouble ...
    }
    // Redundant "bool local" parameter :-(
    public DataElement(ElementKind kind, string name, bool local)
        : this(kind, name)
    {
        // What to do when "local" is false ???
        // ... other code for local (non-persistent) elements ...
    }
}
public class Program
{
    public void Run()
    {
        DataElement data = new DataElement(ElementKind.Regular, "Element");
        DataElement parI = new DataElement(ElementKind.IntParam, "IntElement", 22);
        DataElement parD = new DataElement(ElementKind.DoubleParam, "DblElement", 3.14);
        DataElement locl = new DataElement(ElementKind.Local, "LocalElement");
    }
}

然后,我尝试了更多面向对象的方式来按类型区分构造函数,同时在 Run() 方法中保留相同的初始化代码:

public class ElementKind
{
    public class RegularElement
    {
        internal RegularElement() { /* disallow direct creation */ }
    }
    public class IntParamElement
    {
        internal IntParamElement() { /* disallow direct creation */ }
    }
    public class DoubleParamElement
    {
        internal DoubleParamElement() { /* disallow direct creation */ }
    }
    public class LocalElement
    {
        internal LocalElement() { /* disallow direct creation */ }
    }
    public static readonly ElementKind.RegularElement Regular = new RegularElement();
    public static readonly ElementKind.IntParamElement IntParam = new IntParamElement();
    public static readonly ElementKind.DoubleParamElement DoubleParam = new DoubleParamElement();
    public static readonly ElementKind.LocalElement Local = new LocalElement();
}
public class DataElement
{
    private string FName;
    public string Name { get { return FName; } }
    private bool FPersistent;
    public bool Persistent { get { return FPersistent; } }
    protected DataElement(string name)
    {
        FName = name;
        // ... other initialization ...
    }
    public DataElement(ElementKind.RegularElement kind, string name)
        : this(name)
    {
        FPersistent = true;
    }
    public DataElement(ElementKind.IntParamElement kind, string name, int defaultInt)
        : this(name)
    {
        FPersistent = true;
        // ... use defaultInt ...
    }
    public DataElement(ElementKind.DoubleParamElement kind, string name, double defaultDouble)
        : this(name)
    {
        FPersistent = true;
        // ... use defaultDouble ...
    }
    public DataElement(ElementKind.LocalElement kind, string name)
        : this(name)
    {
        FPersistent = false;
        // ... other code for "local" elements ...
    }
}
public class Program
{
    public void Run()
    {
        DataElement data = new DataElement(ElementKind.Regular, "Element");
        DataElement parI = new DataElement(ElementKind.IntParam, "IntElement", 22);
        DataElement parD = new DataElement(ElementKind.DoubleParam, "DblElement", 3.14);
        DataElement locl = new DataElement(ElementKind.Local, "LocalElement");
    }
}

一切都编译并且运行良好。那么我的问题是什么?.NET Framework设计指南,以及一个名为Microsoft FxCop的工具。通过此工具运行最后一个代码后,我遇到了多个中断性问题(见下文)。

问题是:如何设计我的类以符合 .NET 设计准则和最佳做法?

突破 - 确定性 90% - 嵌套类型不应可见 - 元素种类+常规元素突破 - 确定性 90% - 嵌套类型不应可见 - ElementKind+IntParamElement突破 - 确定性 90% - 嵌套类型不应可见 - ElementKind+DoubleParamElement突破 - 确定性 90% - 嵌套类型不应可见 - 元素种类+本地元素

突破 - 确定性 90% - 静态持有者类型不应具有构造函数 - ElementKind

突破 - 确定性 75% - 标识符不应包含类型名称 - DataElement.#.ctor(ElementKind+IntParamElement,System.String,System.Int32)

突破 - 确定性 75% - 标识符不应包含类型名称 - DataElement.#.ctor(ElementKind+DoubleParamElement,System.String,System.Double)

不间断 - 确定性 25% - 不声明只读可变引用类型 - ElementKind.#Regular

非中断 - 确定性 25% - 不声明只读可变引用类型 - ElementKind.#IntParam

不间断 - 确定性 25% - 不声明只读可变引用类型 - ElementKind.#DoubleParam

不间断 - 确定性 25% - 不声明只读可变引用类型 - ElementKind.#Local

C# 中的重载构造函数类似于 Delphi(具有多个名称)

首先,我会用enum替换嵌套的"ElementKind"class

public enum ElementKind
{
    RegularElement,
    IntParamElement,
    DoubleParamElement,
    LocalElement
}

此外,我认为您的 Delphi 代码不需要映射到构造函数。 您可能最好使用返回DataElement的静态工厂方法。 例如:

public static DataElement Create(string name)
{
    return new DataElement(ElementKind.Regular, name);
}
public static DataElement CreateParam(string name, int defaultInt);
{
    return new DataElement(ElementKind.IntParam, name);
    // ... use defaultInt ... 
}
// similar to above
public static DataElement CreateParam(string name, double defaultDouble); 
public static DataElement CreateLocal(string name);

由于您使用工厂函数来创建 DataElement 对象,因此应将构造函数设为私有。

然后,您将更新 Run() 函数以使用这些函数,如下所示:

public void Run() 
{ 
    DataElement data = DataElement.Create("Element");
    DataElement parI = DataElement.CreateParam("IntElement", 22);
    DataElement parD = DataElement.CreateParam("DblElement", 3.14); 
    DataElement locl = DataElement.CreateLocal("LocalElement"); 
} 

更新:我包含了对Run()函数的建议更改,并更正了基本的Create()工厂方法(我相信它应该返回"常规"DataElement)。

你可以为此使用静态工厂方法:

public class TDataElement {
    private TDataElement(){}
    public static TDataElement Create(string name ) {  
        return new TDataElement {
                                   FName = name,
                                   FPersistent = true
                                   // ... other initialization ...
                                };
    }
    public static TDataElement CreateParam(string name, double defaultDouble){
         var element = Create(name);
         // ... use DefaultInt ...
         return element;
    }
//...    
}

您可以通过以下方式使用它:

Data = TDataElement.Create("Element");
ParI = TDataElement.CreateParam("IntElement", 22);

这是众所周知的做法。例如,在.NET framework的类Image中有一个工厂方法:var image = Image.FromFile("test.jpg");

我认为

,这个Delphi代码是C#中泛型的良好候选者:

class DataElement
{
  public string Name { get; set; }
  public bool Persistent { get; set; }
  public DataElement(/* ctor params */)
  {
  }
}
class DataElement<T> : DataElement
{
  public DataElement(string name, T defaultValue /* other ctor params */)
    : base(/* base ctor params */)
  {
  }
}
class IntElement : DataElement<int> {}
class DoubleElement : DataElement<double> {}

有关TDataElement的更多详细信息将很有用。

最后,由于@JonSenchyna的想法和@Dennis的建议,我想出了以下通用实现。

作为一个奖励功能,我IElementFactory接口添加到组合中,以表达实现子类的要求,以具有所有InitXyz()方法 - 这在原始 Delphi 代码中是没有的。

在这个设计中,我唯一不喜欢的是使DataElement()ByteElement()的默认构造函数成为公共构造函数的要求(来自new()约束),以及InitXyz()方法(因为它们是公共IElementFactory接口的一部分)。

public enum ElementKind
{
    RegularElement,
    IntParamElement,
    DoubleParamElement,
    LocalElement
}
public interface IElementFactory<TElement>
{
    void InitElement(string name);
    void InitParam(string name, int defaultValue);
    void InitParam(string name, double defaultValue);
    void InitLocal(string name);
}
public class DataElement<TElement>
    where TElement : class, IElementFactory<TElement>, new()
{
    private ElementKind FKind;
    public ElementKind Kind { get { return FKind; } }
    private string FName;
    public string Name { get { return FName; } }
    private int FDefaultInt;
    protected int DefaultInt
    {
        get { return FDefaultInt; }
        set { FDefaultInt = value; }
    }
    private double FDefaultDouble;
    protected double DefaultDouble
    {
        get { return FDefaultDouble; }
        set { FDefaultDouble = value; }
    }
    protected DataElement()
    {
    }
    public virtual void InitElement(ElementKind kind, string name)
    {
        FKind = kind;
        FName = name;
    }
    public static TElement Create(string name)
    {
        TElement obj = new TElement();
        obj.InitElement(name);
        return obj;
    }
    public static TElement CreateParam(string name, int defaultValue)
    {
        TElement obj = new TElement();
        obj.InitParam(name, defaultValue);
        return obj;
    }
    public static TElement CreateParam(string name, double defaultValue)
    {
        TElement obj = new TElement();
        obj.InitParam(name, defaultValue);
        return obj;
    }
    public static TElement CreateLocal(string name)
    {
        TElement obj = new TElement();
        obj.InitLocal(name);
        return obj;
    }
}
public class ByteElement : DataElement<ByteElement>, IElementFactory<ByteElement>
{
    public ByteElement()
    {
    }
    public void InitElement(string name)
    {
        base.InitElement(ElementKind.RegularElement, name);
    }
    public void InitParam(string name, int defaultValue)
    {
        base.InitElement(ElementKind.IntParamElement, name);
        // Range checking
        if ((defaultValue >= Byte.MinValue) && (defaultValue <= Byte.MaxValue))
        {
            DefaultInt = defaultValue;
        }
    }
    public void InitParam(string name, double defaultValue)
    {
        base.InitElement(ElementKind.DoubleParamElement, name);
        // Range checking
        if ((defaultValue >= Byte.MinValue) && (defaultValue <= Byte.MaxValue))
        {
            DefaultDouble = defaultValue;
        }
    }
    public void InitLocal(string name)
    {
        base.InitElement(ElementKind.LocalElement, name);
    }
}

看看重构的Program.Run()方法是多么美丽、善意和类似于德尔菲代码(!):

public class Program
{
    public static void Run()
    {
        ByteElement data = ByteElement.Create("Element");
        ByteElement parI = ByteElement.CreateParam("IntElement", 22);
        ByteElement parD = ByteElement.CreateParam("DblElement", 3.14);
        ByteElement locl = ByteElement.CreateLocal("LocalElement");
    }
}

现在 FxCop 只抱怨四种工厂方法,但我想我可以忍受:

中断 - 确定性 95% - 不要在泛型类型上声明静态成员 - DataElement'1.#Create(System.String)

中断 - 确定性 95% - 不声明泛型类型的静态成员 - DataElement'1.#CreateParam(System.String,System.Int32)

突破 - 确定性 95% - 不要在泛型类型上声明静态成员 - DataElement'1.#CreateParam(System.String,System.Double)

中断 - 确定性 95% - 不要在泛型类型上声明静态成员 - DataElement'1.#CreateLocal(System.String)