代码之家  ›  专栏  ›  技术社区  ›  scope_creep

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

  •  6
  • scope_creep  · 技术社区  · 15 年前

    我计划用一个元组来标记每种类型,即这样的转换形式:

    host.name.string:host.dotquad.string
    

    它将提供从输入到输出形式的转换。例如,名称存储在string类型的host字段中,输入将转换为string类型的dotquad表示法并存储回host字段。更复杂的转换可能需要几个步骤,每个步骤都由一个方法调用完成,因此需要方法链。

    进一步检查上面的示例,元组'host.name.string'的字段为hostwww.domain.com. DNS查找是为了将域名转换为IP地址。应用另一种方法将DNS查找返回的类型更改为string类型的dotquad的内部类型。对于这个转换,有4个独立的方法被调用来从一个元组转换成另一个元组。其他一些转换可能需要更多的步骤。

    理想情况下,我想要一个如何在运行时构建方法链的小例子。开发时方法链接相对来说很简单,但是需要一页又一页的代码来覆盖所有可能的内容,并且需要40+次转换。

    我想到的一种方法是,在启动时解析元组,然后将链写入程序集,对其进行编译,然后使用反射加载/访问。这将是真的丑陋和否定的性能提高,我希望获得。

    我用的是单声道,所以没有C#4.0

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

    4 回复  |  直到 15 年前
        1
  •  1
  •   Igor Zevaka    15 年前

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

    Chainer 类,该类接受表示 MethodChain 子类。以这样的集合为例:

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

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

    Chainer.CreateChain 将返回一个lambda调用 MethodChain.Execute()

    希望你能把它融入你的架构。

    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
  •   Ryan Hayes    15 年前

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

    这种模式可以在.NET2.0中实现。

        3
  •  1
  •   madaboutcode    15 年前

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

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

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

    主要优势:

    • 无运行时开销
    • 易于添加/删除/更改转换(代码生成器将负责代码更改:))

    你怎么认为?

        4
  •  1
  •   Lucas    14 年前

    很抱歉代码转储太长,而且它是用Java编写的,而不是用C#,但是我发现您的问题非常有趣,而且我没有太多C#方面的经验。希望您能够毫无困难地调整此解决方案。

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

    • 字符串->整数->浮动->十进制的

    您可能希望选择第二个,因为它将减少转换失败的机会。

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