在运行时创建和存储方法链的方法

时间:2010-07-20 00:33:32

标签: c#-2.0 chaining

我遇到的问题是我需要进行大约40次转换才能将松散类型的信息转换为存储在db,xml文件等中的强类型信息。

我打算用元组标记每种类型,即这样的转换形式:

host.name.string:host.dotquad.string

将提供从输入到输出表单的转换。例如,名称存储在字符串类型的主机字段中,输入转换为字符串类型的dotquad表示法并存储回主机字段。更复杂的转换可能需要几个步骤,每个步骤都是通过方法调用完成的,因此方法链接。

进一步检查上面的例子,元组'host.name.string'的字段主机名为www.domain.com。执行DNS查找以将域名转换为IP地址。应用另一种方法将DNS查找返回的类型更改为string类型的dotquad的内部类型。对于这种转换,有4个单独的方法被称为从一个元组转换为另一个元组。其他一些转换可能需要更多步骤。

理想情况下,我想了解一个在运行时如何构造方法链的小例子。开发时间方法链接相对简单,但需要页面和代码页来覆盖所有可能性,并且需要40多次转换。

我想做的一种方法是,在启动时解析元组,并将链写出到程序集,编译它,然后使用反射来加载/访问。它真的很丑陋,否定了我希望获得的性能提升。

我正在使用Mono,所以没有C#4.0

任何帮助将不胜感激。 鲍勃。

4 个答案:

答案 0 :(得分:1)

command pattern 适用于此处。您可以做的是排队命令,因为您需要对不同的数据类型执行不同的操作。然后,这些消息都可以被处理,并在您稍后准备好时调用适当的方法。

此模式可以在.NET 2.0中实现。

答案 1 :(得分:1)

这是一个使用LINQ表达式的快速而肮脏的解决方案。你已经表明你想要C#2.0,这是3.5,但它确实在Mono 2.6上运行。链接方法有点hacky,因为我不知道你的版本是如何工作的,所以你可能需要调整表达式代码以适应。

真正的魔法确实发生在Chainer类中,它带有一组字符串,代表MethodChain子类。采取这样的集合:

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

这将生成如下链:

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

Chainer.CreateChain将返回一个调用MethodChain.Execute()的lambda。因为Chainer.CreateChain使用了一点反射,所以它很慢,但它只需要为每个表达链运行一次。 lambda的执行几乎与调用实际代码一样快。

希望您能够将其融入您的架构中。

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

答案 2 :(得分:1)

你真的需要在执行时这样做吗?你不能用代码生成创建操作组合吗?

让我详细说明:

假设您有一个名为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...
    }
}

将所有这些包装在一个很好的小执行方法中,如下所示:

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

只有在方法签名发生更改或决定添加新转换时,才需要执行此代码生成应用程序。

主要优势:

  • 没有运行时开销
  • 轻松添加/删除/更改转换(代码生成器将负责代码更改:))

您怎么看?

答案 3 :(得分:1)

我为长代码转储以及使用Java而不是C#这一事实道歉,但我发现你的问题非常有趣,而且我没有太多的C#经验。希望您能够毫不费力地调整此解决方案。

解决问题的一种方法是为每次转换创建一个成本 - 通常这与转换的准确性有关 - 然后执行搜索以找到从一种类型到另一种类型的最佳转换序列

需要成本函数的原因是在多个转换路径中进行选择。例如,从整数转换为字符串是无损的,但不能保证每个字符串都可以用整数表示。所以,如果你有两个转换链

  • string - &gt;整数 - &gt;浮动 - &gt;小数
  • string - &gt;浮动 - &gt;小数

您可能希望选择第二个,因为它会降低转换失败的可能性。

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