如何在预先存在的类中在运行时创建方法?

时间:2015-08-05 12:13:41

标签: c# dynamic

我使用流畅的模式来帮助进行单元测试和结果对象构建。流利的构建器模式的最大痛点是必须为我可能想要设置的每个属性定义所有这些With____方法。

当涉及到一个可能需要设置30个字段的对象时,我并不想写出30个方法,这些方法几乎都做同样的事情。我宁愿写出一些能够处理所有类似逻辑的动态。

例如(伪代码)

for each property in this.properties 
  define method( property.name with 
    return type: this.class,
    parameter types: [property.type]){

    set property property.name, parameters[0]
    return this
  }

以下是我目前在c#

中的内容
 var properties = typeof(EngineModelBuilder).GetProperties();
 foreach (var property in properties){
   // how do I create a method here with the property?
   // a property has .PropertyType and a .Name
   // and the return type is always going to be 'this'
 }

作为参考,以下是普通的流利构建器方法的外观:

    public EngineModelBuilder WithDescription(string description)
    {
        _description = description;
        return this;
    }

1 个答案:

答案 0 :(得分:0)

这是我提出的课程。它利用C#的动态对象来使用运行时功能的“method_missing”方法。

请注意,method_missing来自Ruby,通常用于此类动态行为。

DynamicBuilder.cs

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace PGenCore
{
    /// <summary>
    /// TODO: test different field retreival techniques (using fields from T, and managing in a Dictionary)
    ///  - how would we define defaults?
    ///
    ///
    /// Usage:
    ///   dynamic objectBuilder = new ObjectBuilder();
    ///   var object = objectBuilder
    ///     .WithObjectFieldName(appropriateValue)
    ///     .WithObjectField(appropriateValue2)
    ///     .Build();
    ///
    /// </summary>
    /// <typeparam name="T">Type to Build</typeparam>
    public class DynamicBuilder<T> : DynamicObject
    {
        #region List of FieldInformation
        protected FieldInfo[] FieldInfos;
        public FieldInfo[] FieldInformation
        {
            get
            {
                // don't GetFields all the time
                if (FieldInfos != null) return FieldInfos;

                FieldInfos = this.GetType().GetFields(
                    BindingFlags.Public |
                    BindingFlags.NonPublic |
                    BindingFlags.Instance);

                return FieldInfos;
            }
        }
        #endregion

        #region Utility

        /// <summary>
        /// converts FieldName to _fieldName
        /// </summary>
        /// <param name="publicName"></param>
        /// <returns></returns>
        public static string PublicNameToPrivate(string publicName)
        {
            var propertyLowerFirstLetterName = char.ToLower(
                publicName[0]) + publicName.Substring(1);

            var privateName = "_" + propertyLowerFirstLetterName;

            return privateName;
        }
        #endregion

        #region Method is Missing? Check for With{FieldName} Pattern

        /// <summary>
        /// Inherited form DynamicObject.
        /// Ran before each method call.
        ///
        ///
        /// Note: Currently only works for setting one value
        ///  at a time.
        ///   e.g.: instance.Object = value
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="args"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryInvokeMember(
             InvokeMemberBinder binder,
             object[] args,
             out object result)
        {

            var firstArgument = args[0];
            var methodName = binder.Name;
            var propertyRootName = methodName;

            // following the builder pattern,
            // methods that participate in building T,
            // return this so we can chain building methods.
            result = this;

            // for booleans, since the field / property should be named as
            // a question, using "With" doesn't make sense.
            // so, this logic is only needed when we are not setting a
            // boolean field.
            if (!(firstArgument is bool))
            {
                // if we are not setting a bool, and we aren't starting
                // with "With", this method is not part of the
                // fluent builder pattern.
                if (!methodName.Contains("With")) return false;
                propertyRootName = methodName.Replace("With", "");
            }

            // convert to _privateFieldName
            // TODO: think about dynamicly having fields in a Dictionary,
            //  rather than having to specify them
            var builderFieldName = PublicNameToPrivate(propertyRootName);

            // find matching field name, given the method name
            var fieldInfo = FieldInformation
                .FirstOrDefault(
                    field => field.Name == builderFieldName
                 );

            // if the field was not found, abort
            if (fieldInfo == null) return false;

            // set the field to the value in args
            fieldInfo.SetValue(this, firstArgument);
            return true;
        }

        #endregion

        /// <summary>
        /// Returns the built object
        /// </summary>
        /// <returns></returns>
        public virtual T Build()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// for any complex associations
        /// - building lists of items
        /// - building anything else that isn't just an easy vavlue.
        /// </summary>
        public virtual void SetRelationshipDefaults()
        {
            throw new NotImplementedException();
        }
    }
}

可以在此处查看使用情况测试:https://github.com/NullVoxPopuli/Procedural-Builder/blob/master/ProceduralBuilder.Test/DynamicBuilderTest.cs