Is there a way to have variants in C# besides using the visitor pattern?
Asked Answered
W

5

6

There is no direct support for variant types (aka tagged unions, discriminated unions) in C#. However one can go with a visitor pattern that enables discrimination via double-dispatching and guarantees that all cases are addressed at the compile time. However it's tedious to implement. I wonder if there is more effortless way to get: some sort of variants with a discrimination mechanism that guarantees that all cases of a union are addressed at the compile time in 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;
        }
    }

}
Washcloth answered 14/10, 2013 at 1:40 Comment(19)
Generics?.... post some code depicting what you're looking for.Commercialize
Generics what? If I knew what I am looking for I would not ask a question. As I said the visitor patten is a way to simulate variants by enabling multi-dispatch it takes quite a lot of typing to get things connected and it's something that bothers a lot. I am wonderimg if anyone has a better (less typing yet still statically checked) way to go about it. If you are asking about what a visitor pattern is here is some code codeproject.com/Articles/588882/…Livelong
Seems pretty much useless to me, considering the many advanced C# features such as Action<T> and generics. I still don't understand what you're after.Commercialize
I don't understand what "statically verified" means in the context of a discriminated union, or what the visitor pattern has to do with it. StackOverflow works best when the question is specific and about actual code.Susurrous
If you're looking for an programming construct that simplifies the "implementation" of the Visitor pattern, use delegates. Action<T> is the most notable which allows you to perform any sort of "visit" operation. Your "visitable" class(data structure) would need to have a method that accepts an Action or Action<T> and invoke the action in effect executing the "visit"Foiled
@EricLippert, The visitor pattern, when implemented correctly, guaranties that all cases of a variant type are considered. By saying "statically verified" I meant checked by the compiler, as opposed to, say, dynamic dispatching that doesnt guarantee the totallity.Livelong
Your point is, I think, that if (1) you have a set of objects that are all of a tagged union type, and (2) you correctly write a visitor pattern base class that dispatches virtual methods based on the tag, then you can make a derived class that customizes that dispatch. Since by assumption the base class is a correct visitor then there is a virtual method for every possible tag. (This then gives you double-virtual dispatch.) Your question is whether there is an easier way to get a similar guarantee without writing a visitor pattern base class?Susurrous
@EricLippert, you got it right, basically I wish there was a way not to have a separate (base) visitor class at all. See, for example, F# has discriminated unions and pattern matching that guarantees at the compile time that all cases are addressed. C# doesnt have this feature and the only alternative is the ugly visitors. Hence my question.Livelong
If you could give an example of the sort of syntax you'd like to see - even if it's completely valid C# - that would really help. I'm still struggling to work out what a good answer to this question might look like...Oconnell
Discriminated unions from F# are good examples msdn.microsoft.com/en-us/library/dd233226.aspxLivelong
@AlekseyBykov: Yes, but that's clearly not C#-like syntax. What kind of syntax would you expect within C#? In my experience I normally just use a switch statement where the tag is an enum type... you could always wrap that into a method accepting delegates for any particular type, but that isn't general-purpose.Oconnell
@JonSkeet, the problem with switch is that it's vulnerable to refactoring, namely to adding new cases. The solely reason for having to bother with the visitor pattern is to tackle this problem (to make sure that all new cases are addressed everywhere). Speaking of syntax, I don't know, all I want is to save some typing having to deal with visitors. I hope there is a lighter way to do that. I don' t know what it is though. There wasn't a question if I knew. I am surprised there are so few people to who what I am talking about makes sense. I can't believe nobody else faced a question like thisLivelong
I'd normally rely on unit tests to check the switch statement - it's pretty easy to do. I think what you're looking for simply doesn't exist in C#, I'm afraid.Oconnell
@Aleksey - perhaps if no one really knows what you're talking about it's not an ideal design? I for one am confused as to why you would want to use a variant type struct. Could you provide an example of your 'solution' that requires too much coding which you're attempting to avoid? Perhaps this would give us a better idea of what you're trying to accomplish/optimize.Hyperon
@Hyperon +1. Also, I find in practice that concepts initially implemented as discriminated unions soon accrete enough behavior that they are better converted into an interface/ABC and implementing classes (possibly immutable), so why not code it that way from the start? Anyway, F# and C# are based on different paradigms and it is unreasonable to expect C# to be able to express F# constructs in a natural manner.Beethoven
-1 to @Moho. I don't use C# regularly, but variants are such a common use case in programming (there's a new question on SO about how to implement them in Java every few days) that it is a scandal in my view that none of the widely used languages can do them well.Marathon
@JudgeMental - the variant/tagged union's obsolescence is demonstrated by such lack of supportHyperon
added an example to illustrate what I am talking aboutLivelong
@Moho, You may be confusing the union construct from C/C++ with the very idea of variant types. The first may very well be obsolete, but the second, implemented via classic OO decomposition or Visitor, never will be, as demonstrated by the fact that newer languages such as Scala and F# have first-class support for it.Marathon
W
0

So I ended up using a bunch of delegates instead of the visitor interface. It is a variation of a method that some people here have suggested earlier. Obviously it saves me a class, a trouble of making a closure by hands, and ultimately I have to type much less than I had to to before with visitors. The exhaustiveness (all cases being considered) is guaranteed as long as the GetProcessed method is implemented correctly. The only trouble is that C# has "void" (lack of a result value) thing, which is tackled by a nominal type Nothing that represents absence of a value.

// 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;
            }
        );
    }
Washcloth answered 22/10, 2013 at 16:19 Comment(0)
K
4

Are you looking for something along the lines of Boost Variants? If so, I don't think a direct porting is possible, because C++ template language and C# generics are somewhat different. Moreover boost::variant uses the visitor pattern. Anyway, if you want, you can write something similar. For example (and please note that this code is only a proof of concept), you may define two generic types for visitors and variants:

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");
        }
    }
}

And you can use those types like this:

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>'
}

This way, the compiler can verify that you are passing the right visitor to the right variant, and the VariantVisitor interface forces you to implement the Visit method for all the types of the variant. Obviously, you can also define variants with more than two parameters:

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>
...

But personally I don't like this approach, and I'd rather turn Visit methods into lambdas and pass them as parameters where needed, as pointed out in the comments above. For example, you could write some kind of poor man's pattern matching, adding this method to class 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");
        }
    }

And use it like this:

[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);
}

But note that real pattern matching has way more features: guards, exhaustiveness checks, fragile pattern matching checks, etc.

Kelsey answered 18/10, 2013 at 15:38 Comment(0)
D
2

No way. There is not a concept like using visitor pattern at compile time, because implementation of your visitor pattern runs at runtime through instantiating your classes with use of polymorphism, double-dispatching, on object instances at runtime. Double-dispatching can run only on real object instances at run time, it is not related with compile time. Additionally the "Discrimination Mechanism" must run on your objects and if you are talking about objects, you are at runtime..

Darrickdarrill answered 21/10, 2013 at 8:35 Comment(0)
D
1

I found a couple of articles that might help you:

Either in C#: http://siliconcoding.wordpress.com/2012/10/26/either_in_csharp/

Discriminated Unions (I): http://www.drdobbs.com/cpp/discriminated-unions-i/184403821

Discriminated Unions (II): http://www.drdobbs.com/cpp/discriminated-unions-ii/184403828

Detour answered 20/10, 2013 at 17:22 Comment(0)
W
0

So I ended up using a bunch of delegates instead of the visitor interface. It is a variation of a method that some people here have suggested earlier. Obviously it saves me a class, a trouble of making a closure by hands, and ultimately I have to type much less than I had to to before with visitors. The exhaustiveness (all cases being considered) is guaranteed as long as the GetProcessed method is implemented correctly. The only trouble is that C# has "void" (lack of a result value) thing, which is tackled by a nominal type Nothing that represents absence of a value.

// 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;
            }
        );
    }
Washcloth answered 22/10, 2013 at 16:19 Comment(0)
H
-1

Represents an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

This structural code demonstrates the Visitor pattern in which an object traverses an object structure and performs the same operation on each node in this structure. Different visitor objects define different operations.

using System; using System.Collections;

class MainApp { static void Main() { // Setup structure 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();
}

}

// "Visitor" abstract class Visitor { public abstract void VisitConcreteElementA( ConcreteElementA concreteElementA); public abstract void VisitConcreteElementB( ConcreteElementB concreteElementB); }

// "ConcreteVisitor1" class ConcreteVisitor1 : Visitor { public override void VisitConcreteElementA( ConcreteElementA concreteElementA) { Console.WriteLine("{0} visited by {1}", concreteElementA.GetType().Name, this.GetType().Name); }

public override void VisitConcreteElementB(
  ConcreteElementB concreteElementB)
{
  Console.WriteLine("{0} visited by {1}",
    concreteElementB.GetType().Name, this.GetType().Name);
}

}

// "ConcreteVisitor2" class ConcreteVisitor2 : Visitor { public override void VisitConcreteElementA( ConcreteElementA concreteElementA) { Console.WriteLine("{0} visited by {1}", concreteElementA.GetType().Name, this.GetType().Name); }

public override void VisitConcreteElementB(
  ConcreteElementB concreteElementB)
{
  Console.WriteLine("{0} visited by {1}",
    concreteElementB.GetType().Name, this.GetType().Name);
}

}

// "Element" abstract class Element { public abstract void Accept(Visitor visitor); }

// "ConcreteElementA" class ConcreteElementA : Element { public override void Accept(Visitor visitor) { visitor.VisitConcreteElementA(this); }

public void OperationA()
{
}

}

// "ConcreteElementB" class ConcreteElementB : Element { public override void Accept(Visitor visitor) { visitor.VisitConcreteElementB(this); }

public void OperationB()
{
}

}

// "ObjectStructure" class ObjectStructure { private ArrayList elements = new ArrayList();

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

}

Huntress answered 24/10, 2013 at 12:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.