我正在尝试将一些代码从 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 设计指南和最佳实践?
Breaking - 确定性 90% - 嵌套类型不可见 - ElementKind+RegularElement Breaking - 确定性 90% - 嵌套类型不可见 - ElementKind+IntParamElement Breaking - 确定性 90% - 嵌套类型不可见 - ElementKind+DoubleParamElement Breaking -确定性 90% - 嵌套类型不应可见 - ElementKind+LocalElement
Breaking - 确定性 90% - 静态持有者类型不应该有构造函数 - ElementKind
打破 - 确定性 75% - 标识符不应包含类型名称 - DataElement.#.ctor(ElementKind+IntParamElement,System.String,System.Int32)
打破 - 确定性 75% - 标识符不应包含类型名称 - DataElement.#.ctor(ElementKind+DoubleParamElement,System.String,System.Double)
Non Breaking - 确定性 25% - 不要声明只读可变引用类型 - ElementKind.#Regular
Non Breaking - 确定性 25% - 不要声明只读可变引用类型 - ElementKind.#IntParam
Non Breaking - 确定性 25% - 不要声明只读可变引用类型 - ElementKind.#DoubleParam
Non Breaking - 确定性 25% - 不要声明只读可变引用类型 - ElementKind.#Local