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の指定や整数値の受け渡しなど、間違いの可能性
- ローカル要素を処理するには、追加のブールパラメータが必要です
以下の最初の試み:
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の設計ガイドライン、およびMicrosoftFxCopという名前のツール。このツールで最後のコードを実行した後、複数の重大な問題が発生しました(以下を参照)。
そして問題は、.NET設計ガイドラインとベストプラクティスに準拠するようにクラスを設計する方法です。
Breaking-確実性90%-ネストされたタイプは表示されない-ElementKind + RegularElement Breaking-確実性90%-ネストされたタイプは表示されない-ElementKind + IntParamElement Breaking-確実性90%-ネストされたタイプは表示されない-ElementKind + DoubleParamElement Breaking-確実性90%-ネストされたタイプは表示されないはずです-ElementKind + LocalElement
速報-確実性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