Метод для создания и хранения цепочки методов во время выполнения - PullRequest
6 голосов
/ 20 июля 2010

Проблема, с которой я столкнулся, заключается в том, что мне нужно выполнить около 40+ преобразований, чтобы преобразовать свободно вводимую информацию в строго типизированную информацию, хранящуюся в db, xml-файле и т. Д.

Я планирую пометить каждый тип кортежем, то есть трансформационной формой, подобной этой:

host.name.string:host.dotquad.string

, который предложит преобразование из формы ввода в форму вывода. Например, имя хранится в поле хоста типа string, входные данные преобразуются в точечную запись типа string и сохраняются обратно в поле host. Для более сложных преобразований может потребоваться несколько шагов, причем каждый шаг выполняется вызовом метода, следовательно, цепочка методов.

Рассматривая далее пример выше, кортеж 'host.name.string' с полем host имени www.domain.com. DNS-поиск выполняется для преобразования доменного имени в IP-адрес. Другой метод применяется для изменения типа, возвращаемого поиском DNS, на внутренний тип dotquad типа string. Для этого преобразования есть 4 отдельных метода, вызываемых для преобразования одного кортежа в другой. Некоторые другие преобразования могут потребовать больше шагов.

В идеале я хотел бы привести небольшой пример того, как цепочки методов строятся во время выполнения. Цепочка методов времени разработки относительно тривиальна, но потребует страниц и страниц кода, чтобы охватить все возможные варианты, с более чем 40 преобразованиями.

Один из способов, который я задумал сделать, - это анализ кортежей при запуске и запись цепочек в сборку, компиляция, а затем использование отражения для загрузки / доступа. Это было бы очень уродливо и сводило бы на нет повышение производительности, которое я надеюсь получить.

Я использую Mono, поэтому нет C # 4.0

Любая помощь будет оценена. Боб.

Ответы [ 4 ]

1 голос
/ 05 августа 2010

Я прошу прощения за длинный дамп кода и тот факт, что это на Java, а не на C #, но я нашел вашу проблему довольно интересной, и у меня нет большого опыта C #. Надеюсь, вы сможете без проблем адаптировать это решение.

Один из подходов к решению вашей проблемы состоит в том, чтобы создать стоимость для каждой конверсии - обычно это связано с точностью конверсии - и затем выполнить поиск, чтобы найти наилучшую возможную последовательность конверсий для перехода от одного типа к другому. .

Причиной необходимости использования функции стоимости является выбор между несколькими путями преобразования. Например, преобразование целого числа в строку без потерь, но нет гарантии, что каждая строка может быть представлена ​​целым числом. Так что, если у вас было две цепочки преобразования

  • string -> integer -> float -> decimal
  • string -> float -> decimal

Вы хотели бы выбрать второй, потому что это уменьшит вероятность сбоя преобразования.

Приведенный ниже код Java реализует такую ​​схему и выполняет поиск в лучшем случае, чтобы найти оптимальную последовательность преобразования. Я надеюсь, что вы найдете это полезным. Выполнение кода приводит к следующему выводу:

> No conversion possible from string to integer 
> The optimal conversion sequence from string to host.dotquad.string is:
>               string to     host.name.string, cost = -1.609438
>     host.name.string to             host.dns, cost = -1.609438 *PERFECT*
>             host.dns to         host.dotquad, cost = -1.832581
>         host.dotquad to  host.dotquad.string, cost = -1.832581 *PERFECT*

Вот код Java.

/**
 * Use best-first search to find an optimal sequence of operations for
 * performing a type conversion with maximum fidelity.
 */
import java.util.*;

public class TypeConversion {

    /**
     * Define a type-conversion interface.  It converts between to
     * user-defined types and provides a measure of fidelity (accuracy)
     * of the conversion.
     */
    interface ITypeConverter<T, F> {
        public T convert(F from);
        public double fidelity();

        // Could use reflection instead of handling this explicitly
        public String getSourceType();
        public String getTargetType();
    }

    /**
     * Create a set of user-defined types.
     */
    class HostName {
        public String hostName;
        public HostName(String hostName) { 
            this.hostName = hostName; 
        }
    }

    class DnsLookup {
        public String ipAddress;    
        public DnsLookup(HostName hostName) {
            this.ipAddress = doDNSLookup(hostName);
        }
        private String doDNSLookup(HostName hostName) {
            return "127.0.0.1";
        }
    }

    class DottedQuad {
        public int[] quad = new int[4];
        public DottedQuad(DnsLookup lookup) {
            String[] split = lookup.ipAddress.split(".");
            for ( int i = 0; i < 4; i++ )
                quad[i] = Integer.parseInt( split[i] );
        }
    }

    /**
     * Define a set of conversion operations between the types. We only
     * implement a minimal number for brevity, but this could be expanded.
     * 
     * We start by creating some broad classes to differentiate among
     * perfect, good and bad conversions.
     */
    abstract class PerfectTypeConversion<T, F> implements ITypeConverter<T, F> {
        public abstract T convert(F from);
        public double fidelity() { return 1.0; }
    }

    abstract class GoodTypeConversion<T, F> implements ITypeConverter<T, F> {
        public abstract T convert(F from);
        public double fidelity() { return 0.8; }
    }

    abstract class BadTypeConversion<T, F> implements ITypeConverter<T, F> {
        public abstract T convert(F from);
        public double fidelity() { return 0.2; }
    }

    /**
     * Concrete classes that do the actual conversions.
     */
    class StringToHostName extends BadTypeConversion<HostName, String> {
        public HostName convert(String from) { return new HostName(from); }
        public String getSourceType() { return "string"; }
        public String getTargetType() { return "host.name.string"; }
    }

    class HostNameToDnsLookup extends PerfectTypeConversion<DnsLookup, HostName> {
        public DnsLookup convert(HostName from) { return new DnsLookup(from); }
        public String getSourceType() { return "host.name.string"; }
        public String getTargetType() { return "host.dns"; }
    }

    class DnsLookupToDottedQuad extends GoodTypeConversion<DottedQuad, DnsLookup> {
        public DottedQuad convert(DnsLookup from) { return new DottedQuad(from); }
        public String getSourceType() { return "host.dns"; }
        public String getTargetType() { return "host.dotquad"; }
    }

    class DottedQuadToString extends PerfectTypeConversion<String, DottedQuad> {
        public String convert(DottedQuad f) { 
            return f.quad[0] + "." + f.quad[1] + "." + f.quad[2] + "." + f.quad[3]; 
        }
        public String getSourceType() { return "host.dotquad"; }
        public String getTargetType() { return "host.dotquad.string"; }
    }

    /**
     * To find the best conversion sequence, we need to instantiate
     * a list of converters.
     */
    ITypeConverter<?,?> converters[] = 
    { 
       new StringToHostName(),
       new HostNameToDnsLookup(),
       new DnsLookupToDottedQuad(),
       new DottedQuadToString()
    };

    Map<String, List<ITypeConverter<?,?>>> fromMap = 
        new HashMap<String, List<ITypeConverter<?,?>>>();

    public void buildConversionMap() 
    {
        for ( ITypeConverter<?,?> converter : converters ) 
        {
            String type = converter.getSourceType();
            if ( !fromMap.containsKey( type )) {
                fromMap.put( type, new ArrayList<ITypeConverter<?,?>>());
            }

            fromMap.get(type).add(converter);
        }
    }

    public class Tuple implements Comparable<Tuple> 
    {
        public String type;
        public double cost;
        public Tuple parent;

        public Tuple(String type, double cost, Tuple parent) {
            this.type = type;
            this.cost = cost;
            this.parent = parent;
        }

        public int compareTo(Tuple o) {
            return Double.compare( cost, o.cost );
        }
    }

    public Tuple findOptimalConversionSequence(String from, String target)
    {
        PriorityQueue<Tuple> queue = new PriorityQueue<Tuple>();

        // Add a dummy start node to the queue
        queue.add( new Tuple( from, 0.0, null ));

        // Perform the search
        while ( !queue.isEmpty() )
        {
            // Pop the most promising candidate from the list
            Tuple tuple = queue.remove();

            // If the type matches the target type, return
            if ( tuple.type == target )
                return tuple;

            // If we have reached a dead-end, backtrack
            if ( !fromMap.containsKey( tuple.type ))
                continue;

            // Otherwise get all of the possible conversions to
            // perform next and add their costs
            for ( ITypeConverter<?,?> converter : fromMap.get( tuple.type ))
            {
                String type = converter.getTargetType();
                double cost = tuple.cost + Math.log( converter.fidelity() );

                queue.add( new Tuple( type, cost, tuple ));
            }
        }

        // No solution
        return null;
    }

    public static void convert(String from, String target)
    {
        TypeConversion tc = new TypeConversion();

        // Build a conversion lookup table
        tc.buildConversionMap();

        // Find the tail of the optimal conversion chain.
        Tuple tail = tc.findOptimalConversionSequence( from, target );

        if ( tail == null ) {
            System.out.println( "No conversion possible from " + from + " to " + target );
            return;
        }

        // Reconstruct the conversion path (skip dummy node)
        List<Tuple> solution = new ArrayList<Tuple>();
        for ( ; tail.parent != null ; tail = tail.parent ) 
            solution.add( tail );

        Collections.reverse( solution );

        StringBuilder sb = new StringBuilder();
        Formatter formatter = new Formatter(sb);

        sb.append( "The optimal conversion sequence from " + from + " to " + target + " is:\n" );
        for ( Tuple tuple : solution ) {
            formatter.format( "%20s to %20s, cost = %f", tuple.parent.type, tuple.type, tuple.cost );       

            if ( tuple.cost == tuple.parent.cost )
                sb.append( " *PERFECT*");

            sb.append( "\n" );      
        }

        System.out.println( sb.toString() );
    }

    public static void main(String[] args) 
    {
        // Run two tests
        convert( "string", "integer" );
        convert( "string", "host.dotquad.string" );     
    }
}
1 голос
/ 29 июля 2010

Вот быстрое и грязное решение с использованием выражений LINQ.Вы указали, что хотите C # 2.0, это 3.5, но он работает на Mono 2.6.Цепочка методов немного хакерская, поскольку я точно не знаю, как работает ваша версия, поэтому вам, возможно, придется настроить код выражения в соответствии с требованиями.

Настоящее волшебство действительно происходит в классе Chainer, которыйпринимает набор строк, которые представляют подкласс MethodChain.Возьмите коллекцию, подобную этой:

{
"string",
"string",
"int"
}

Это сгенерирует цепочку, подобную этой:

new StringChain(new StringChain(new IntChain()));

Chainer.CreateChain вернет лямбду, которая вызывает MethodChain.Execute().Поскольку Chainer.CreateChain использует немного отражения, он медленный, но его нужно запускать только один раз для каждой цепочки выражений.Выполнение лямбды почти так же быстро, как и вызов реального кода.

Надеюсь, вы сможете вписать это в свою архитектуру.

public abstract class MethodChain {
private MethodChain[] m_methods;
    private object m_Result;


    public MethodChain(params MethodChain[] methods) {
        m_methods = methods;
    }

    public MethodChain Execute(object expression) {

        if(m_methods != null) {
            foreach(var method in m_methods) {
                expression = method.Execute(expression).GetResult<object>();
            }
        }

        m_Result = ExecuteInternal(expression);
        return this;
    }

    protected abstract object ExecuteInternal(object expression);

    public T GetResult<T>() {
        return (T)m_Result;
    }
}

public class IntChain : MethodChain {

    public IntChain(params MethodChain[] methods)
        : base(methods) {

    }

    protected override object ExecuteInternal(object expression) {
        return int.Parse(expression as string);
    }
}

public class StringChain : MethodChain {

    public StringChain(params MethodChain[] methods):base(methods) {

    }

    protected override object ExecuteInternal(object expression) {
        return (expression as string).Trim();
    }
}


public class Chainer {

    /// <summary>
    /// methods are executed from back to front, so methods[1] will call method[0].Execute before executing itself
    /// </summary>
    /// <param name="methods"></param>
    /// <returns></returns>
    public Func<object, MethodChain> CreateChain(IEnumerable<string> methods) {

        Expression expr = null;
        foreach(var methodName in methods.Reverse()) {

            ConstructorInfo cInfo= null;
            switch(methodName.ToLower()) {
                case "string":
                    cInfo = typeof(StringChain).GetConstructor(new []{typeof(MethodChain[])});
                    break;
                case "int":
                    cInfo = typeof(IntChain).GetConstructor(new[] { typeof(MethodChain[]) });
                    break;
            }
            if(cInfo == null)
                continue;

            if(expr != null)
                expr = Expression.New(cInfo, Expression.NewArrayInit( typeof(MethodChain), Expression.Convert(expr, typeof(MethodChain))));
            else
                expr = Expression.New(cInfo, Expression.Constant(null, typeof(MethodChain[])));
        }

        var objParam = Expression.Parameter(typeof(object));
        var methodExpr = Expression.Call(expr, typeof(MethodChain).GetMethod("Execute"), objParam);
        Func<object, MethodChain> lambda = Expression.Lambda<Func<object, MethodChain>>(methodExpr, objParam).Compile();

        return lambda;
    }
    [TestMethod]
    public void ExprTest() {
        Chainer chainer = new Chainer();
        var lambda = chainer.CreateChain(new[] { "int", "string" });
        var result = lambda(" 34 ").GetResult<int>();
        Assert.AreEqual(34, result);
    }
}
1 голос
/ 29 июля 2010

Вам действительно нужно сделать это во время выполнения? Не можете ли вы создать комбинацию операций с использованием генерации кода?

Позвольте мне уточнить:

Предполагается, что у вас есть класс с именем Conversions, который содержит все 40+ преобразований, которые вы упомянули, например:

//just pseudo code.. 
class conversions{

string host_name(string input){}
string host_dotquad(string input){}
int type_convert(string input){}
float type_convert(string input){}
float increment_float(float input){}

} 

Напишите простое консольное приложение или нечто подобное, которое использует отражение для генерации кода для таких методов:

execute_host_name(string input, Queue<string> conversionQueue)
{
    string ouput = conversions.host_name(input);

    if(conversionQueue.Count == 0)
        return output;

    switch(conversionQueue.dequeue())
    {
        // generate case statements only for methods that take in 
        // a string as parameter because the host_name method returns a string. 
        case "host.dotquad": return execute_host_dotquad(output,conversionQueue);
        case "type.convert": return execute_type_convert(output, conversionQueue);
        default: // exception...
    }
}

Оберните все это в хороший маленький метод execute, подобный этому:

object execute(string input, string [] conversions)
{
    Queue<string> conversionQueue = //create the queue..

    case(conversionQueue.dequeue())
    {
        case "host.name": return execute_host_name(output,conversionQueue);
        case "host.dotquad": return execute_host_dotquad(output,conversionQueue);
        case "type.convert": return execute_type_convert(output, conversionQueue);
        default: // exception...
    }
}

Это приложение для генерации кода должно выполняться только при изменении сигнатур вашего метода или когда вы решаете добавить новые преобразования.

Основные преимущества:

  • Нет времени выполнения
  • Легко добавлять / удалять / изменять преобразования (генератор кода позаботится об изменениях кода :))

Что вы думаете?

1 голос
/ 28 июля 2010

Шаблон команды подходит здесь. То, что вы можете сделать, это поставить команды в очередь, так как вам нужны разные операции, выполняемые для разных типов данных. Затем все эти сообщения могут быть обработаны и вызывать соответствующие методы, когда вы будете готовы позже.

Этот шаблон может быть реализован в .NET 2.0.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...