c# - How do I create methods at run time in a pre-existing class? -
i'm using fluent pattern helping unit testing, , result object building. biggest pain point of fluent builder pattern having define these with____ methods each , every property might want set.
when comes object maybe 30 fields might want set, don't want write out 30 methods pretty same thing. i'd rather write out dynamic can handle similar logic me.
for example (psuedo code)
for each property in this.properties define method( property.name return type: this.class, parameter types: [property.type]){ set property property.name, parameters[0] return }
here have far in c#
var properties = typeof(enginemodelbuilder).getproperties(); foreach (var property in properties){ // how create method here property? // property has .propertytype , .name // , return type going 'this' }
for reference, here how normal fluent builder method looks:
public enginemodelbuilder withdescription(string description) { _description = description; return this; }
this class came with. utilizes c#'s dynamic object use "method_missing" approach of run-time functionality.
note method_missing ruby, , commonly used kind of dynamic behavior.
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 t, , managing in dictionary) /// - how define defaults? /// /// /// usage: /// dynamic objectbuilder = new objectbuilder(); /// var object = objectbuilder /// .withobjectfieldname(appropriatevalue) /// .withobjectfield(appropriatevalue2) /// .build(); /// /// </summary> /// <typeparam name="t">type build</typeparam> public class dynamicbuilder<t> : dynamicobject { #region list of fieldinformation protected fieldinfo[] fieldinfos; public fieldinfo[] fieldinformation { { // don't getfields time if (fieldinfos != null) return fieldinfos; fieldinfos = this.gettype().getfields( bindingflags.public | bindingflags.nonpublic | bindingflags.instance); return fieldinfos; } } #endregion #region utility /// <summary> /// converts fieldname _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 missing? check with{fieldname} pattern /// <summary> /// inherited form dynamicobject. /// ran before each method call. /// /// /// note: works setting 1 value /// @ 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 builder pattern, // methods participate in building t, // return can chain building methods. result = this; // booleans, since field / property should named // question, using "with" doesn't make sense. // so, logic needed when not setting // boolean field. if (!(firstargument bool)) { // if not setting bool, , aren't starting // "with", method not part of // fluent builder pattern. if (!methodname.contains("with")) return false; propertyrootname = methodname.replace("with", ""); } // convert _privatefieldname // todo: think dynamicly having fields in dictionary, // rather having specify them var builderfieldname = publicnametoprivate(propertyrootname); // find matching field name, given method name var fieldinfo = fieldinformation .firstordefault( field => field.name == builderfieldname ); // if field not found, abort if (fieldinfo == null) return false; // set field value in args fieldinfo.setvalue(this, firstargument); return true; } #endregion /// <summary> /// returns built object /// </summary> /// <returns></returns> public virtual t build() { throw new notimplementedexception(); } /// <summary> /// complex associations /// - building lists of items /// - building else isn't easy vavlue. /// </summary> public virtual void setrelationshipdefaults() { throw new notimplementedexception(); } } }
tests of usage can seen here: https://github.com/nullvoxpopuli/procedural-builder/blob/master/proceduralbuilder.test/dynamicbuildertest.cs
Comments
Post a Comment