Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/289.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
除了使用访问者模式外,还有没有其他方法可以在C#中使用变体?_C#_Variant_Visitor Pattern - Fatal编程技术网

除了使用访问者模式外,还有没有其他方法可以在C#中使用变体?

除了使用访问者模式外,还有没有其他方法可以在C#中使用变体?,c#,variant,visitor-pattern,C#,Variant,Visitor Pattern,在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 tha

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

}
//这是一种变体类型。每次只能容纳一个案例(一个值)
//来自预定义的一组案例。实现此接口的所有类
//构成变体的有效案例集。因此,每次都会出现一个变体
//是实现此接口的类之一的实例。为了
//将新案例添加到变量中必须有另一个类实现
//这个界面。
公共界面动物
{
//此方法将当前保留的案例介绍给使用/处理的人
//变体。通过处理,我们的意思是将案例转化为结果
//由泛型类型TResult表示的值。
TResult GetProcessed(IsoAnimalProcessor处理器);
}
//这是一个尴尬的部分,我们每次都需要访客
//处理变体。对于每种可能的情况,该处理器都有相应的
//方法,将该案例转换为结果值。
公共接口处理器
{
TResult ProcessCat(Cat-Cat);
TResult ProcessFish(鱼类);
}
//代表动物变种猫的案例。
公猫:动物
{
公共猫头{get;set;}
公共CatsBody主体{get;set;}
公共CatsTail尾部{get;set;}
公共IEnumerable Legs{get;set;}
public TResult GetProcessed(IsoAnimalProcessor处理器)
{
//对于这种情况,处理器对变量的每种情况都有一种方法
//在特定情况下(作为一只猫),我们总是选择ProcessCat方法
返回processor.ProcessCat(此);
}
}
//代表动物变种鱼类的案例。
公营鱼类:野生动物
{
公共鱼头{get;set;}
公共鱼体{get;set;}
公共鱼尾{get;set;}
public TResult GetProcessed(IsoAnimalProcessor处理器)
{
//对于这种情况,处理器对变量的每种情况都有一种方法
//在特殊情况下(作为一条鱼),我们总是选择ProcessCat方法
返回处理器.ProcessFish(this);
}
}
公共静态类动物画家
{
//现在,为了处理一个变体,在本例中,我们要
//画一幅它呈现的任何动物的图画,我们必须
//创建ISomeAnimalProcessor接口的新实现
//把绘画的逻辑放进去。
公共静态空白添加动物图片(图片、动物)
{
var animaltopictureder=新的animaltopictureder(图片);
动物。获得加工(动物图形学读取器);
}
//在每次需要处理变体时创建新访问者:
//1.需要大量的打字。
//2.使类型系统膨胀。
//3.使代码更难维护。
//4.使代码可读性降低。
私有类AnimalTopicReader:IsoAnimalProcessor
{
私人图片;
公共动物读取器(图片)
{
这个图片=图片;
}
公共事务处理猫(Cat)
{
this.picture.AddBackground(newsomehouse());
此.picture.Add(cat.Body);
此.picture.Add(猫头);
这个.picture.Add(cat.Tail);
这个.picture.AddAll(猫腿);
不返回任何内容;
}
公众鱼(鱼)
{
this.picture.AddBackground(new someunder());
这个。图片。添加(鱼。身体);
这个。图片。添加(鱼。尾巴);
这个。图片。添加(鱼。头);
不返回任何内容;
}
}
}

您是否在寻找类似的产品?如果是这样,我认为不可能直接移植,因为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");
        }
    }
}
但就我个人而言,我不喜欢这种方法,我宁愿将
Visit
方法转换为lambda,并在需要时将它们作为参数传递,正如上面的评论所指出的那样。例如,您可以编写某种穷人模式匹配,将此方法添加到类
Variant

public R匹配(Func f1,Func f2)
{
如果(_item1Set)
{
返回f1(此.Item1);
}
else if(_item2Set)
{
返回f2(本条第2项);
}
其他的
{
抛出新的InvalidOperationException(“未设置变量”);
}
}
然后像这样使用它:

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>'
}
[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);
}
[测试]
公共void TestMatch()
{
var v=新变量();
v、 组(10);
变量r1=v.匹配(
i=>i*2,
s=>s.Length);
AreEqual(20,r1);
v、 设置(“测试”);
变量r2=v.匹配(
i=>i.ToString(),
s=>s+s);
AreEqual(“testtest”,r2);
}

但请注意,真正的模式匹配有更多的功能:防护、穷尽性检查、脆弱的模式匹配检查等。

我发现了几篇文章可能对您有所帮助:

在C#中:

歧视工会(一):


歧视工会(II):

不可能。并没有像在编译时使用访问者模式这样的概念,因为访问者模式的实现在运行时t运行
    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);
}
// 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;
            }
        );
    }
  // 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();
}
public override void VisitConcreteElementB(
  ConcreteElementB concreteElementB)
{
  Console.WriteLine("{0} visited by {1}",
    concreteElementB.GetType().Name, this.GetType().Name);
}
public override void VisitConcreteElementB(
  ConcreteElementB concreteElementB)
{
  Console.WriteLine("{0} visited by {1}",
    concreteElementB.GetType().Name, this.GetType().Name);
}
public void OperationA()
{
}
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);
  }
}