除了使用访问者模式之外,有没有办法在 C# 中拥有变体
本文关键字:拥有 有没有 访问者 模式 | 更新日期: 2023-09-27 18:35:08
C# 中没有对变体类型(又名标记联合、可区分联合)的直接支持。但是,可以使用访客模式,通过双重调度进行区分,并保证在编译时解决所有情况。但是,实施起来很乏味。我想知道是否有更轻松的方法可以得到:某种具有区分机制的变体,可以保证在 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++模板语言和 C# 泛型有些不同。此外,boost::variant
使用访客模式。无论如何,如果你愿意,你可以写类似的东西。例如(请注意,此代码只是一个概念证明),您可以为访问者和变体定义两种泛型类型:
public interface VariantVisitor<T, U>
{
void Visit(T item);
void Visit(U item);
}
public class Variant<T, U>
{
public T Item1 { get; private set; }
private bool _item1Set;
public U Item2 { get; private set; }
private bool _item2Set;
public Variant()
{
}
public void Set(T item)
{
this.Item1 = item;
_item1Set = true;
_item2Set = false;
}
public void Set(U item)
{
this.Item2 = item;
_item1Set = false;
_item2Set = true;
}
public void ApplyVisitor(VariantVisitor<T, U> visitor)
{
if (_item1Set)
{
visitor.Visit(this.Item1);
}
else if (_item2Set)
{
visitor.Visit(this.Item2);
}
else
{
throw new InvalidOperationException("Variant not set");
}
}
}
你可以像这样使用这些类型:
private static object _result;
internal class TimesTwoVisitor : VariantVisitor<int, string>
{
public void Visit(int item)
{
_result = item * 2;
}
public void Visit(string item)
{
_result = item + item;
}
}
[Test]
public void TestVisitVariant()
{
var visitor = new TimesTwoVisitor();
var v = new Variant<int, string>();
v.Set(10);
v.ApplyVisitor(visitor);
Assert.AreEqual(20, _result);
v.Set("test");
v.ApplyVisitor(visitor);
Assert.AreEqual("testtest", _result);
var v2 = new Variant<double, DateTime>();
v2.Set(10.5);
//v2.ApplyVisitor(visitor);
// Argument 1: cannot convert from 'TestCS.TestVariant.TimesTwoVisitor' to 'TestCS.TestVariant.VariantVisitor<double,System.DateTime>'
}
这样,编译器可以验证您是否将正确的访问者传递给正确的变体,并且VariantVisitor
接口强制您为所有类型的变体实现 Visit
方法。显然,您还可以定义具有两个以上参数的变体:
public interface VariantVisitor<T, U, V>
...
public interface VariantVisitor<T, U, V, W>
...
public class Variant<T, U, V>
...
public class Variant<T, U, V, W>
...
但我个人不喜欢这种方法,我宁愿将Visit
方法转换为 lambda,并在需要时将它们作为参数传递,如上面的评论中所述。例如,你可以编写某种穷人的模式匹配,将此方法添加到类Variant<T, U>
:
public R Match<R>(Func<T, R> f1, Func<U, R> f2)
{
if (_item1Set)
{
return f1(this.Item1);
}
else if (_item2Set)
{
return f2(this.Item2);
}
else
{
throw new InvalidOperationException("Variant not set");
}
}
并像这样使用它:
[Test]
public void TestMatch()
{
var v = new Variant<int, string>();
v.Set(10);
var r1 = v.Match(
i => i * 2,
s => s.Length);
Assert.AreEqual(20, r1);
v.Set("test");
var r2 = v.Match(
i => i.ToString(),
s => s + s);
Assert.AreEqual("testtest", r2);
}
但请注意,真正的模式匹配具有更多功能:守卫、穷举性检查、脆弱模式匹配检查等。
没办法。没有像在编译时使用访问者模式这样的概念,因为访问者模式的实现通过在运行时在对象实例上使用多态性(双重调度)实例化类来在运行时运行。双重调度只能在运行时在真实对象实例上运行,与编译时无关。此外,"区分机制"必须在您的对象上运行,如果您谈论的是对象,那么您处于运行时。
我找到了几篇可能对你有帮助的文章:
要么在 C# 中:http://siliconcoding.wordpress.com/2012/10/26/either_in_csharp/
受歧视工会(一):http://www.drdobbs.com/cpp/discriminated-unions-i/184403821
受歧视工会(二):http://www.drdobbs.com/cpp/discriminated-unions-ii/184403828
所以我最终使用了一堆代表而不是访问者界面。这是这里一些人之前建议的方法的变体。显然,它为我节省了一堂课,用手关闭的麻烦,最终我必须比以前与访客一起打字少得多。只要正确实现 GetProcessingding方法,就可以保证详尽性(考虑所有情况)。唯一的麻烦是 C# 有"void"(缺少结果值)的东西,它由表示缺少值的名义类型 Nothing 来解决。
// This is a variant type. At each single time it can 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 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 any possible case the variant can hold to a processing
// function that turns the value of that case into some result.
// Using delegates instead of an interface saves us a lot of typing!
TResult GetProcessed<TResult>(
Func<Cat, TResult> processCat,
Func<Fish, TResult> processFish
);
}
// 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>(
Func<Cat, TResult> processCat,
Func<Fish, TResult> processFish
) {
// for this particular case (being a cat) we pick the processCat delegate
return 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>(
Func<Cat, TResult> processCat,
Func<Fish, TResult> processFish
) {
// for this particular case (being a fish) we pick the processFish method
return processFish(this);
}
}
public static class AnimalPainter
{
// Now, in order to process a variant, in this case we stil want to
// add an animal to a picture, we don't need a visitor anymore.
// All the painting logic stays within the same method.
// Which is:
// 1. Much less typing.
// 2. More readable.
// 3. Easier to maintain.
public static void AddAnimalToPicture(Picture picture, ISomeAnimal animal)
{
animal.GetProcessed<Nothing>(
cat =>
{
picture.AddBackground(new SomeHouse());
picture.Add(cat.Body);
picture.Add(cat.Head);
picture.Add(cat.Tail);
picture.AddAll(cat.Legs);
return Nothing.AtAll;
},
fish =>
{
picture.AddBackground(new SomeUnderwater());
picture.Add(fish.Body);
picture.Add(fish.Tail);
picture.Add(fish.Head);
return Nothing.AtAll;
}
);
}
表示要对对象结构的元素执行的操作。Visitor 允许您定义新操作,而无需更改其操作的元素的类。
此结构代码演示了 Visitor 模式,在该模式中,对象遍历对象结构并在此结构中的每个节点上执行相同的操作。不同的访问者对象定义不同的操作。
使用系统;使用系统集合;
类主应用 { 静态空隙 主() { 设置结构 ObjectStructure o = new ObjectStructure(); o.Attach(new ConcreteElementA()); o.Attach(new ConcreteElementB());
// Create visitor objects
ConcreteVisitor1 v1 = new ConcreteVisitor1();
ConcreteVisitor2 v2 = new ConcreteVisitor2();
// Structure accepting visitors
o.Accept(v1);
o.Accept(v2);
// Wait for user
Console.Read();
}
}
"访客" 抽象类访客 { 公共抽象空隙 访问混凝土元素A( ConcreteElementA concreteElementA); public abstract void VisitConcreteElementB( 混凝土元素B 混凝土元素B); }
"ConcreteVisitor1" 类混凝土访客1:访客 { public override void VisitConcreteElementA( ConcreteElementA concreteElementA) { Console.WriteLine("{0} visited by {1}", concreteElementA.GetType().名字,这个。GetType()。姓名); }
public override void VisitConcreteElementB(
ConcreteElementB concreteElementB)
{
Console.WriteLine("{0} visited by {1}",
concreteElementB.GetType().Name, this.GetType().Name);
}
}
"ConcreteVisitor2" 类混凝土访客2:访客 { public override void VisitConcreteElementA( ConcreteElementA concreteElementA) { Console.WriteLine("{0} visited by {1}", concreteElementA.GetType().名字,这个。GetType()。姓名); }
public override void VisitConcreteElementB(
ConcreteElementB concreteElementB)
{
Console.WriteLine("{0} visited by {1}",
concreteElementB.GetType().Name, this.GetType().Name);
}
}
"元素" 抽象类元素 { 公共摘要无效 接受(访客访客); }
"ConcreteElementA" 类 混凝土元素A : 元素 { 公共覆盖无效 接受(访客访客) { 游客。访问混凝土元素A(这个); }
public void OperationA()
{
}
}
"ConcreteElementB" 类 混凝土元素B : 元素 { 公共覆盖无效 接受(访客访客) { 游客。访问混凝土元素B(这个); }
public void OperationB()
{
}
}
"对象结构" 类对象结构 { 私有数组列表元素 = 新的数组列表();
public void Attach(Element element)
{
elements.Add(element);
}
public void Detach(Element element)
{
elements.Remove(element);
}
public void Accept(Visitor visitor)
{
foreach (Element e in elements)
{
e.Accept(visitor);
}
}
}