我正在尝试将一些代码从Delphi移植到C#,并且我发现了一种我无法以合理的方式实现的构造,同时遵守.NET Framework设计指南(我在问题的最后处理)。< / p>
显然,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#构造函数中,必须与该类具有相同的名称,因此首先我尝试将行为与枚举区分开来。唉,我偶然发现了几个问题:
首次尝试如下:
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 - Certainty 90% - 嵌套类型不应该是可见的 - ElementKind + RegularElement 打破 - 确定性90% - 嵌套类型不应该是可见的 - ElementKind + IntParamElement 打破 - 确定性90% - 嵌套类型不应该是可见的 - ElementKind + DoubleParamElement 打破 - 确定性90% - 嵌套类型不应该是可见的 - ElementKind + LocalElement
Breaking - Certainty 90% - 静态持有者类型不应该有构造函数 - ElementKind
Breaking - Certainty 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
答案 0 :(得分:7)
对于初学者,我会用[{1}}替换嵌套的“ElementKind”class
:
enum
此外,我认为您的Delphi代码不需要映射到构造函数。使用返回public enum ElementKind
{
RegularElement,
IntParamElement,
DoubleParamElement,
LocalElement
}
的静态factory methods可能会更好。例如:
DataElement
由于您正在使用工厂函数来创建DataElement对象,因此您应该将构造函数设为私有。
然后,您将更新Run()函数以使用它们,如下所示:
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);
更新:我在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()
工厂方法(我相信它应该返回“常规”{{1 }})。
答案 1 :(得分:1)
您可以使用静态工厂方法:
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框架的Image
类中有一个工厂方法:var image = Image.FromFile("test.jpg");
答案 2 :(得分:0)
我认为,这个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的更多详细信息将非常有用。
答案 3 :(得分:0)
最后,由于@JonSenchyna的想法和@Dennis的建议,我提出了以下通用实现。
作为一个额外的功能,我在混合中添加了IElementFactory
接口,表达了实现子类以获得所有InitXyz()
方法的要求 - 这在原始Delphi代码中是不存在的。
我在此设计中唯一不喜欢的是制作DataElement()
和ByteElement()
public(来自new()
约束)的默认构造函数的要求,以及{ {1}}方法(因为它们是公共InitXyz()
接口的一部分)。
IElementFactory
看看有多精美,善意和类似于Delphi代码(!)的重构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()
现在FxCop只抱怨了四种工厂方法,但我认为我可以接受这种方法:
Breaking - Certainty 95% - 不要在泛型类型上声明静态成员 - DataElement`1。#Create(System.String)
Breaking - Certainty 95% - 不要在泛型类型上声明静态成员 - DataElement`1。#CreateParam(System.String,System.Int32)
Breaking - Certainty 95% - 不要在泛型类型上声明静态成员 - DataElement`1。#CreateParam(System.String,System.Double)
Breaking - Certainty 95% - 不要在泛型类型上声明静态成员 - DataElement`1。#CreateLocal(System.String)