Как имитировать анонимные классы в С#

Я пишу небольшую библиотеку структур данных на С# и столкнулся с архитектурной проблемой. По сути, у меня есть класс, который реализует шаблон посетителя, и существует множество возможных реализаций посетителей:

public interface ITreeVisitor<T, U>
{
    U Visit(Nil<T> s);
    U Visit(Node<T> s);
}

public abstract class Tree<T> : IEnumerable<T>
{
    public readonly static Tree<T> empty = new Nil<T>();
    public abstract U Accept<U>(ITreeVisitor<T, U> visitor);
}

public sealed class Nil<T> : Tree<T>
{
    public override U Accept<U>(ITreeVisitor<T, U> visitor) { return visitor.Visit(this); }
}

public sealed class Node<T> : Tree<T>
{
    public Tree<T> Left { get; set; }
    public T Value { get; set; }
    public Tree<T> Right { get; set; }

    public override U Accept<U>(ITreeVisitor<T, U> visitor) { return visitor.Visit(this); }
}

Каждый раз, когда я хочу передать посетителя, я должен создать класс посетителя, реализовать интерфейс и передать его следующим образом:

class InsertVisitor<T> : ITreeVisitor<T, Tree<T>> where T : IComparable<T>
{
    public T v { get; set; };

    public Tree<T> Visit(Nil<T> s)
    {
        return new Node<T>() { Left = Tree<T>.empty, Value = v, Right = Tree<T>.empty };
    }

    public Tree<T> Visit(Node<T> s)
    {
        switch (v.CompareTo(s.Value))
        {
            case -1: return new Node<T>() { Left = Insert(v, s.Left), Value = s.Value, Right = s.Right };
            case 1: return new Node<T>() { Left = s.Left, Value = s.Value, Right = Insert(v, s.Right) };
            default: return s;
        }
    }
}

public static Tree<T> Insert<T>(T value, Tree<T> tree) where T : IComparable<T>
{
    return tree.Accept<Tree<T>>(new InsertVisitor<T>() { v = value });
}

Мне не нравится писать так много стандартного кода, потому что он становится очень запутанным, когда у вас есть нетривиальное количество реализаций посетителей.

Я хочу написать что-то похожее на анонимные классы Java (код концепции):

public static Tree<T> Insert<T>(T v, Tree<T> tree) where T : IComparable<T>
{
    return tree.Accept<Tree<T>>(new InsertVisitor<T>()
        {
            public Tree<T> Visit(Nil<T> s) { return new Node<T>() { Left = Tree<T>.empty, Value = v, Right = Tree<T>.empty }; }
            public Tree<T> Visit(Node<T> s)
            {
                switch (v.CompareTo(s.Value))
                {
                    case -1: return new Node<T>() { Left = Insert(v, s.Left), Value = s.Value, Right = s.Right };
                    case 1: return new Node<T>() { Left = s.Left, Value = s.Value, Right = Insert(v, s.Right) };
                    default: return s;
                 }
            }
        };
}

Есть ли способ имитировать анонимные классы с реализацией интерфейса на C#?


person Juliet    schedule 15.01.2010    source источник
comment
Возможно, вы захотите объяснить, что такое анонимный интерфейс. Боюсь, я понятия не имею, что это значит.   -  person Noldorin    schedule 15.01.2010
comment
@Noldorin: анонимный интерфейс - не лучший выбор слов, я имею в виду анонимный класс. В Java есть функция, с помощью которой вы можете реализовывать интерфейсы на лету, не нуждаясь в именованном классе — я хотел бы сделать что-то подобное в C#.   -  person Juliet    schedule 15.01.2010
comment
Вы уверены, что не можете обойтись делегатами?   -  person Lasse V. Karlsen    schedule 15.01.2010
comment
В C# анонимные типы не могут реализовывать интерфейсы. Дополнительные сведения см. здесь: msdn.microsoft.com/ en-us/library/bb397696%28VS.100%29.aspx   -  person Scott Anderson    schedule 15.01.2010
comment
Я не уверен, правильно ли я понял, но не могли бы вы вместо интерфейса посетителя создать базовый класс посетителя с общей логикой?   -  person recursive    schedule 15.01.2010
comment
См. раздел stackoverflow.com /вопросы/508853/   -  person pedro    schedule 15.01.2010
comment
@Джульетта: брось курить F#   -  person Mauricio Scheffer    schedule 15.01.2010
comment
@Mauricio: Виновен по обвинению ;) На самом деле я пытаюсь написать библиотеку неизменяемых структур данных на C #, может быть, даже начать проект с открытым исходным кодом в коде Google, и я использую идиомы программирования F # в качестве основы. Анонимные классы полезны, потому что они сохраняют всю логику обхода дерева, содержащуюся в одном и том же методе, и (по крайней мере, внешне) напоминают сопоставление с образцом.   -  person Juliet    schedule 15.01.2010
comment
@Juliet: как насчет code.google.com/p/functional-dotnet code.msdn.microsoft.com/BclExtras   -  person Mauricio Scheffer    schedule 15.01.2010


Ответы (1)


Вы можете изменить «поведенческую» часть класса с методов, определенных для интерфейса, на делегаты, вызываемые в соответствующее время, и создать нового посетителя, передав новые делегаты, тем самым привлекая анонимные функции для выполнения работы анонимных классов.

Код скетча (не тестировался, можно подчистить как надо):

class CustomVisitor<T> : ITreeVisitor<T, Tree<T>> where T : IComparable<T>
{
    public T v { get; set; };
    public Func<Nil<T>,  Tree<T>> VisitNil  { get; set; }
    public Func<Node<T>, Tree<T>> VisitNode { get; set; }

    public Tree<T> Visit(Nil<T>  s) { return VisitNil(s);  }
    public Tree<T> Visit(Node<T> s) { return VisitNode(s); }
}

public static Tree<T> Insert<T>(T v, Tree<T> tree) where T : IComparable<T>
{
    return tree.Accept<Tree<T>>(new CustomVisitor<T> {
        VisitNil  = s =>
            return new Node<T>() { Left = Tree<T>.empty, Value = v, Right = Tree<T>.empty }; }
        VisitNode = s =>
        {
            switch (v.CompareTo(s.Value))
            {
                case -1: return new Node<T>() { Left = Insert(v, s.Left), Value = s.Value, Right = s.Right };
                case  1: return new Node<T>() { Left = s.Left, Value = s.Value, Right = Insert(v, s.Right) };
                default: return s;
             }
        }
    });
}
person mqp    schedule 15.01.2010
comment
+1, +ответ: о, ничего себе, это было проще, чем я думал :) Я беспокоился, что мне придется переписать мой код с использованием if (tree is Nil) { ... } else { ... } только для того, чтобы сохранить всю логику обхода дерева, содержащуюся в одном и том же методе. Очень признателен! - person Juliet; 15.01.2010
comment
Это хороший пример того, как концепции функционального программирования могут повысить ценность объектно-ориентированных языков. знак равно - person Erik Forbes; 15.01.2010