4

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

4

4 に答える 4

7

手始めに、ネストされた「ElementKind」classenum:に置き換えます。

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)。

于 2012-06-07T20:08:59.443 に答える
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);

これはよく知られている方法です。たとえば、Image.NETFrameworkのクラスにファクトリメソッドがあります。var image = Image.FromFile("test.jpg");

于 2012-06-07T20:13:42.583 に答える
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に関する詳細が役立ちます。

于 2012-06-07T20:14:46.760 に答える
0

最後に、@ 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()リファクタリングされたメソッドがどれほど美しく、善意があり、Delphiコード(!)に類似しているかを見てください。

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は4つのファクトリメソッドについてしか文句を言いませんが、私はそれで生きることができると思います:

速報-確実性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)

于 2012-06-13T08:19:07.017 に答える