4

基本記述子を拡張する多くのジェネリックと記述子を備えた流暢なインターフェイスを作成しようとしています。ここにすべてのコードを貼り付けると読めなくなるため、これを github リポジトリに入れました。

型制約に関する Eric Lippert の投稿 ( http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx ) を読んだ後ジェネリック拡張メソッドによる型推論なし主題については少し理解が深まりましたが、まだ質問がありました。

流暢な呼び出しを可能にするいくつかのクラスがあるとします。

var giraffe = new Giraffe();
new ZooKeeper<Giraffe>()
    .Name("Jaap")
    .FeedAnimal(giraffe);

var reptile = new Reptile();
new ExperiencedZooKeeper<Reptile>()
    .Name("Martijn")
    .FeedAnimal(reptile)
    .CureAnimal(reptile);

クラスは次のようになります。

public class ZooKeeper<T>
    where T : Animal
{
    internal string name;
    internal List<T> animalsFed = new List<T>();

    // this method needs to be fluent
    public ZooKeeper<T> Name(string name)
    {
        this.name = name;
        return this;
    }

    // this method needs to be fluent
    public ZooKeeper<T> FeedAnimal(T animal)
    {
        animalsFed.Add(animal);
        return this;
    }
}

public class ExperiencedZooKeeper<T> : ZooKeeper<T>
    where T : Animal
{
    internal List<T> animalsCured = new List<T>();

    // this method needs to be fluent
    // but we must new it in order to be able to call CureAnimal after this
    public new ExperiencedZooKeeper<T> Name(string name)
    {
        base.Name(name);
        return this;
    }

    // this method needs to be fluent
    // but we must new it in order to be able to call CureAnimal after this
    public new ExperiencedZooKeeper<T> FeedAnimal(T animal)
    {
        base.FeedAnimal(animal);
        return this;
    }

    // this method needs to be fluent
    public ExperiencedZooKeeper<T> CureAnimal(T animal)
    {
        animalsCured.Add(animal);
        return this;
    }
}

ExperiencedZooKeeperの実装を隠して「新しい」メソッドを取り除こうとしましたZooKeeper。違いは、newメソッドがExperiencedZooKeeper正しい型を返すことです。私の知る限り、メソッドなしでこれを行う方法はありませんnew

私が試みた別のアプローチは、「セッター」を拡張メソッドに移動することです。これは .Name() メソッドではうまく機能しZooKeeperBaseますが、内部フィールドを含む を導入します。

public abstract class ZooKeeperBase
{
    internal string name;

}

public class ZooKeeper<T> : ZooKeeperBase
    where T : Animal
{
    internal List<T> animalsFed = new List<T>();


    // this method needs to be fluent
    public ZooKeeper<T> FeedAnimal(T animal)
    {
        animalsFed.Add(animal);
        return this;
    }
}

public static class ZooKeeperExtensions
{

    // this method needs to be fluent
    public static TZooKeeper Name<TZooKeeper>(this TZooKeeper zooKeeper, string name)
        where TZooKeeper : ZooKeeperBase
    {
        zooKeeper.name = name;
        return zooKeeper;
    }
}

しかし、この正確なアプローチは FeedAnimal(T animal) では機能しません。追加の型パラメーターが必要です。

// this method needs to be fluent
public static TZooKeeper FeedAnimal<TZooKeeper, T>(this TZooKeeper zooKeeper, T animal)
    where TZooKeeper : ZooKeeper<T>
    where T : Animal
{
    zooKeeper.animalsFed.Add(animal);
    return zooKeeper;
}

これでも問題なく、うまく機能し、流暢に呼び出すことができます。

new ExperiencedZooKeeper<Reptile>()
    .Name("Martijn")
    .FeedAnimal(reptile)
    .CureAnimal(reptile);

実際の問題は、次のメソッドを流暢にしようとしたときに始まります。

public static TZooKeeper Favorite<TZooKeeper, T>(this TZooKeeper zooKeeper, Func<T, bool> animalSelector)
    where TZooKeeper : ZooKeeper<T>
    where T : Animal
{
    zooKeeper.favoriteAnimal = zooKeeper.animalsFed.FirstOrDefault(animalSelector);
    return zooKeeper;
}

Favorite次のように呼び出すことはできません。

new ExperiencedZooKeeper<Reptile>()
  .Name("Eric")
  .FeedAnimal(reptile)
  .FeedAnimal(new Reptile())
  .Favorite(r => r == reptile)

No type inference with generic extension method と同じ問題が発生するためですが、必要な T を記述する Type パラメーター TZookKeeper が既にあるため、このケースは少し複雑です。しかし、Eric Lippert のブログ投稿のように、型制約は署名の一部ではありません。

The type arguments for method 'TestTypeInference5.ZooKeeperExtensions.Favorite<TZooKeeper,T>(TZooKeeper, System.Func<T,bool>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

完全なコードについては、 https://github.com/q42jaap/TestTypeInferenceを参照してください 。このリポジトリの README には、私が解決しようとした実際の問題が実際に説明されています。

new問題は、 ZooKeeper 自体のメソッドを隠してZooKeeper のすべてのサブクラスに ZooKeeper のすべてのメソッドを追加せずに、この流暢なメソッド スタイルを作成する方法があるかどうかです。

4

1 に答える 1

2

1 つの可能性は、各レベルの基本クラスと、そこから派生する空のハンドラー クラスを作成することです。

基本クラス:

public abstract class ZooKeeperBase<TZooKeeper, TAnimal>
    where TZooKeeper : ZooKeeperBase<TZooKeeper, TAnimal>
    where TAnimal : Animal
{
    private string name;
    private List<TAnimal> animalsFed = new List<TAnimal>();
    private TAnimal favoriteAnimal;

    public TZooKeeper Name(string name)
    {
        this.name = name;
        return (TZooKeeper)this;
    }

    public TZooKeeper FeedAnimal(TAnimal animal)
    {
        animalsFed.Add(animal);
        return (TZooKeeper)this;
    }

    public TZooKeeper Favorite(Func<TAnimal, bool> animalSelector)
    {
        favoriteAnimal = animalsFed.FirstOrDefault(animalSelector);
        return (TZooKeeper)this;
    }
}

public abstract class ExperiencedZooKeeperBase<TZooKeeper, TAnimal>
    : ZooKeeperBase<TZooKeeper, TAnimal>
    where TZooKeeper : ExperiencedZooKeeperBase<TZooKeeper, TAnimal>
    where TAnimal : Animal
{
    private List<TAnimal> animalsCured = new List<TAnimal>();

    public TZooKeeper CureAnimal(TAnimal animal)
    {
        animalsCured.Add(animal);
        return (TZooKeeper)this;
    }
}

ハンドラー クラス:

public class ZooKeeper<T> : ZooKeeperBase<ZooKeeper<T>, T>
    where T : Animal
{
}

public class ExperiencedZooKeeper<T>
    : ExperiencedZooKeeperBase<ExperiencedZooKeeper<T>, T>
    where T : Animal
{
}

使用法は、質問で示したとおりです。

于 2013-05-08T14:15:08.487 に答える