除了使用访问者模式之外,有没有办法在C#中使用变体?

在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;
        }
    }

}

解决方法

你在寻找 Boost Variants的东西吗?如果是这样,我认为不可能直接移植,因为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,V,W>
...

public class Variant<T,V>
...
public class Variant<T,W>
...

但我个人不喜欢这种方法,我宁愿将Visit方法转换为lambdas,并在需要时将它们作为参数传递,如上面的评论中所指出的那样.例如,您可以编写某种穷人的模式匹配,将此方法添加到类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);
}

但请注意,真正的模式匹配具有更多功能:警卫,详尽检查,脆弱的模式匹配检查等.

相关文章

原文地址:http://msdn.microsoft.com/en-us/magazine/cc163...
前言 随着近些年微服务的流行,有越来越多的开发者和团队所采...
最近因为比较忙,好久没有写博客了,这篇主要给大家分享一下...
在多核CPU在今天和不久的将来,计算机将拥有更多的内核,Mic...
c语言输入成绩怎么判断等级
字符型数据在内存中的存储形式是什么