基本記述子を拡張する多くのジェネリックと記述子を備えた流暢なインターフェイスを作成しようとしています。ここにすべてのコードを貼り付けると読めなくなるため、これを 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 のすべてのメソッドを追加せずに、この流暢なメソッド スタイルを作成する方法があるかどうかです。