【发布时间】:2013-10-14 01:40:02
【问题描述】:
在 C# 中没有对 variant types(又名标记联合、区分联合)的直接支持。但是,可以使用visitor pattern,它可以通过双重调度进行区分,并保证所有情况都在编译时得到解决。但是实施起来很繁琐。我想知道是否有更轻松的方法来获得:某种具有区分机制的变体,可以保证在 C# 的编译时处理联合的所有情况?
// This is a variant type. At each single time it can only hold one case (a value)
// from a predefined set of cases. All classes that implement this interface
// consitute the set of the valid cases of the variant. So at each time a variant can
// be an instance of one of the classes that implement this interface. In order to
// add a new case to the variant there must be another class that implements
// this interface.
public interface ISomeAnimal
{
// This method introduces the currently held case to whoever uses/processes
// the variant. By processing we mean that the case is turned into a resulting
// value represented by the generic type TResult.
TResult GetProcessed<TResult>(ISomeAnimalProcessor<TResult> processor);
}
// This is the awkward part, the visitor that is required every time we want to
// to process the variant. For each possible case this processor has a corresponding
// method that turns that case to a resulting value.
public interface ISomeAnimalProcessor<TResult>
{
TResult ProcessCat(Cat cat);
TResult ProcessFish(Fish fish);
}
// A case that represents a cat from the ISomeAnimal variant.
public class Cat : ISomeAnimal
{
public CatsHead Head { get; set; }
public CatsBody Body { get; set; }
public CatsTail Tail { get; set; }
public IEnumerable<CatsLeg> Legs { get; set; }
public TResult GetProcessed<TResult>(ISomeAnimalProcessor<TResult> processor)
{
// a processor has a method for each case of a variant, for this
// particular case (being a cat) we always pick the ProcessCat method
return processor.ProcessCat(this);
}
}
// A case that represents a fish from the ISomeAnimal variant.
public class Fish : ISomeAnimal
{
public FishHead Head { get; set; }
public FishBody Body { get; set; }
public FishTail Tail { get; set; }
public TResult GetProcessed<TResult>(ISomeAnimalProcessor<TResult> processor)
{
// a processor has a method for each case of a variant, for this
// particular case (being a fish) we always pick the ProcessCat method
return processor.ProcessFish(this);
}
}
public static class AnimalPainter
{
// Now, in order to process a variant, in this case we want to
// paint a picture of whatever animal it prepresents, we have to
// create a new implementation of ISomeAnimalProcessor interface
// and put the painting logic in it.
public static void AddAnimalToPicture(Picture picture, ISomeAnimal animal)
{
var animalToPictureAdder = new AnimalToPictureAdder(picture);
animal.GetProcessed(animalToPictureAdder);
}
// Making a new visitor every time you need to process a variant:
// 1. Requires a lot of typing.
// 2. Bloats the type system.
// 3. Makes the code harder to maintain.
// 4. Makes the code less readable.
private class AnimalToPictureAdder : ISomeAnimalProcessor<Nothing>
{
private Picture picture;
public AnimalToPictureAdder(Picture picture)
{
this.picture = picture;
}
public Nothing ProcessCat(Cat cat)
{
this.picture.AddBackground(new SomeHouse());
this.picture.Add(cat.Body);
this.picture.Add(cat.Head);
this.picture.Add(cat.Tail);
this.picture.AddAll(cat.Legs);
return Nothing.AtAll;
}
public Nothing ProcessFish(Fish fish)
{
this.picture.AddBackground(new SomeUnderwater());
this.picture.Add(fish.Body);
this.picture.Add(fish.Tail);
this.picture.Add(fish.Head);
return Nothing.AtAll;
}
}
}
【问题讨论】:
-
对我来说似乎毫无用处,考虑到许多高级 C# 功能,例如
Action<T>和泛型。我还是不明白你在追求什么。 -
我不明白“静态验证”在有区别的联合上下文中是什么意思,或者访问者模式与它有什么关系。当问题具体且与实际代码相关时,StackOverflow 效果最佳。
-
如果您正在寻找一种能够简化访问者模式“实现”的编程结构,请使用委托。 Action
是最值得注意的,它允许您执行任何类型的“访问”操作。您的“可访问”类(数据结构)需要有一个接受 Action 或 Action 并调用实际执行“访问”的操作的方法 -
@EricLippert,访问者模式在正确实施时,可以保证考虑到变体类型的所有情况。通过说“静态验证”,我的意思是由编译器检查,而不是说动态调度不能保证整体性。
-
你的观点是,我认为,如果 (1) 你有一组对象都是标记的联合类型,并且 (2) 你正确地编写了一个访问者模式基类来调度虚拟基于标签的方法,然后您可以创建一个自定义该调度的派生类。由于假设基类是正确的访问者,那么每个可能的标签都有一个虚拟方法。 (这会为您提供双重虚拟调度。)您的问题是,是否有一种更简单的方法可以在不编写访问者模式基类的情况下获得类似的保证?
标签: c# variant visitor-pattern