Dynamically generate property getter/setter through reflection or similar

Go To StackoverFlow.com

5

Imagine the following class:

public class Settings
{
    [FileBackedProperty("foo.txt")]
    public string Foo { get; set; }
}

I would like to be able to write something similar to the above and have settings.Foo read from the file "foo.txt" and settings.Foo = "bar" write to "foo.txt".

Obviously this is a simplified example and I wouldn't do the above in a production application, but there are other examples, like if I wanted Foo to be stored in ASP.net Session state "foo" but I get tired of writing the following code over and over:

public int Foo
{
    get
    {
        if (Session["foo"] != null)
            return Convert.ToInt32(Session["foo"]);
        else
            // Throw an exception or return a default value
    }
    set
    {
        Session["foo"] = value;
    }
}

(Once again this example is simplified and I wouldn't write the above code, actually I'm lying, I have the above code and am working to refactor it, thus this question)

The above example is fine unless you have 50 different session values that all have similar logic. So is there someway I could convert the second property into something similar to the first? (Using attributes and reflection, or maybe some other method?)

2012-04-04 20:12
by thelsdj
If you are using Visual Studio you should take a look at code snippets. They allow you type something short, expand to the desired code and then allow you to fill-in specified areas. Similar to the existing propg, etc - Joshua Drake 2012-04-04 20:26
@JoshuaDrake That seems like just as bad as copy/paste. What if I want to change the implementation? I still have to go back and change every property individually - thelsdj 2012-04-04 20:28
So you want to dynamically add the properties at runtime? Do you have all server machines, or low expectations of performance? Not that you cannot do so, but I would strongly caution against it. If you did not mean at runtime, then you would have to regenerate any code output anyway - Joshua Drake 2012-04-04 20:31


6

I know this is not what you (and also I) needed; but this is the closest without using a third party library. You can change the logic for get&set methods and add some cahcing for GetProperty and GetCustomAttributes methods or if you already have a base class you can write get&set methods as static in a helper class. Again not the perfect answer and also may have a bad performance but at least it decreases the code you copy and paste (:

NOTE: It is important to make properties virtual in order to prevent compiler inlining them.

public class SampleClass : SessionObject
{
    [Session(Key = "SS_PROP")]
    public virtual int SampleProperty
    {
        get { return get(); }
        set { set(value); }
    }

    [Session(Key = "SS_PROP2")]
    public virtual string SampleProperty2
    {
        get { return get(); }
        set { set(value); }
    }
}

[AttributeUsage(AttributeTargets.Property)]
public class SessionAttribute : Attribute
{
    public string Key { get; set; }
}

public abstract class SessionObject
{
    Dictionary<string, object> Session = new Dictionary<string, object>();

    protected void set(object value)
    {
        StackFrame caller = new StackFrame(1);
        MethodBase method = caller.GetMethod();
        string propName = method.Name.Substring(4);
        Type type = method.ReflectedType;
        PropertyInfo pi = type.GetProperty(propName);
        object[] attributes = pi.GetCustomAttributes(typeof(SessionAttribute), true);
        if (attributes != null && attributes.Length == 1)
        {
            SessionAttribute ssAttr = attributes[0] as SessionAttribute;
            Session[ssAttr.Key] = value;
        }
    }

    protected dynamic get()
    {
        StackFrame caller = new StackFrame(1);
        MethodBase method = caller.GetMethod();
        string propName = method.Name.Substring(4);
        Type type = method.ReflectedType;
        PropertyInfo pi = type.GetProperty(propName);
        object[] attributes = pi.GetCustomAttributes(typeof(SessionAttribute), true);
        if (attributes != null && attributes.Length == 1)
        {
            SessionAttribute ssAttr = attributes[0] as SessionAttribute;
            if (Session.ContainsKey(ssAttr.Key))
            {
                return Session[ssAttr.Key];
            }
        }
        return default(dynamic);
    }
}
2012-07-23 08:23
by Mehmet Ataş


3

Another option could be for you is use of PostSharp. You define attributes and it injects an IL in final code, so it's not going to change your source code. Which has its bads and its goods.

This product is not free.

Some Getting started tips.

Hope this helps.

2012-04-04 20:41
by Tigran
You mention it injects IL, is that at compile time or application startup time or something? I'm guessing that PostSharp then gets around the performance problems that AOP using ContextBoundObject has - thelsdj 2012-04-04 20:59
it's a compile time. Naturally there is no a goodness without problems :) The main, I would mantion, imo, is that the final binary that runs is not that one you expect by looking on your code. There are, naturally, also some performance issues too, but it's something that has to be measured on your concrete implementation - Tigran 2012-04-04 21:02
I mean this is industrial class software, so just worth an attention - Tigran 2012-04-04 21:03
Looking here: http://www.sharpcrafters.com/purchase/compare it seems like I could probably do what I need with the free "Starter" edition. I might try it out and see if its possible - thelsdj 2012-04-04 21:12
@thelsdj: it's a good idea. Yes, there is a free trial (should be) - Tigran 2012-04-04 21:19


1

If you want to avoid writing the getter code so much, write a helper method:

public int Foo
{
    get
    {
        return GetHelper<int>("foo");
    }
    set
    {
        Session["foo"] = value;
    }
}

public T GetHelper<T>(string name, T defaultValue = default(T))
{
    if (Session[name] != null)
        return (T)Session[name];
    else
    {
        return defaultValue;
    }
}

If you have access to dynamics, then you can use a dynamic object to wrap the session:

internal class DynamicSession : DynamicObject
{
    private HttpSessionState_session;

    public DynamicSession()
    {
        _session = HttpContext.Current.Session;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (_session[binder.Name] != null)
        {
            result = _session[binder.Name];
            return true;
        }
        result = null;
        return false;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _session[binder.Name] = value;
        return true;
    }
}

And you can then use it like so:

dynamic session = new DynamicSession();
    //These properties are "magically" put in and taken out of session!
//get
int foo = session.Foo;
//set
session.Foo = 3;

A final option is something like Live Templates in Resharper to just make typing the code a lot easier.

2012-04-04 20:34
by vcsjones
hm.. but it seems something OP's trying to avoid actually.. - Tigran 2012-04-04 20:35
@Tigran Yeah, but maybe its just not possible. The above is shorter and has less duplication. It seems bad that there's not a good way to do what I want because it would get rid of a ton of duplication - thelsdj 2012-04-04 20:38
@thelsdj: see my answer, could a good option for you, imo - Tigran 2012-04-04 20:42
@Tigran I included a snippet that takes advantage of the DLR and dynamics to achieve these results - vcsjones 2012-04-04 20:43
Whoopsy, I had a bug. Just edited to fix it - vcsjones 2012-04-04 20:46
@vcsjones The dynamic version is really nice except for the fact that I am on 3.5 right now and I'd also need to be able to control the property name to session variable conversion (though that could be done with a lookup table or something). +1 for other people for whom this is an option - thelsdj 2012-04-04 20:58


1

If you are looking to do this at runtime, see the following Dr. Dobbs article Generating Code at Run Time With Reflection.Emit.

2012-04-04 20:36
by Joshua Drake


1

What you are trying to do is called "Aspect-Oriented Programming" and it's relatively common for certain tasks where the necessary code would otherwise be duplicated many times with only very minor changes. Your example certainly qualifies.

The basic idea is as follows; you create an attribute you can use to decorate classes or class members. The attribute defines a "context" for a message passing system in the CLR that allows you to hook a method interceptor onto the method that will run when it's called.

Understand that there is a significant performance hit involved; the object with members decorated by attributes must inherit from MarshallByRefObject or ContextBoundObject; either one of these will incur about a 10x hit to the performance of the object during runtime, even if you don't actually do any attribute decorating.

Here's some example code: http://www.developerfusion.com/article/5307/aspect-oriented-programming-using-net/3/

You can also use Dynamic Proxies to create objects "on the fly" based on attribute decoration or other reflection-based information. This is the technology behind a LOT of stuff C# developers take for granted, like ORMs, IoC frameworks, etc etc. You would basically use something like Castle DynamicProxy to create an object that looked like your base object, but had overridden definitions of the properties decorated with the attributes that had the file-based population/persistence logic.

2012-04-04 20:39
by KeithS


0

You can also use the DynamicProxy nuget package from Castle.Core to achieve this behaviour.

You can intercept calls to Get and Set methods for all the virtual properties of your class. However all the property getters and setters you want to modify must be virtual.

I have provided a more complete answer here: https://stackoverflow.com/a/48764825/5103354 and a gist is available here.

The following behavior should be observed:

    [Fact]
    public void SuccessFullyRegisterGetAndSetEvents()
    {
        ProxyGenerator generator = new ProxyGenerator();
        var tracked = generator.CreateClassProxy<TrackedClass>(new GetSetInterceptor());
        tracked.SomeContent = "some content";
        Assert.Single(tracked.GetEvents());
        var eventAfterSomeContentAssigned = tracked.GetEvents().Last();
        Assert.Equal(EventType.Set, eventAfterSomeContentAssigned.EventType);
        Assert.Equal("some content", eventAfterSomeContentAssigned.Value);
        Assert.Equal("SomeContent", eventAfterSomeContentAssigned.PropertyInfo.Name);
        tracked.SomeInt = 1;
        Assert.Equal(2, tracked.GetEvents().Count);
        var eventAfterSomeIntAssigned = tracked.GetEvents().Last();
        Assert.Equal(EventType.Set, eventAfterSomeContentAssigned.EventType);
        Assert.Equal(1, eventAfterSomeIntAssigned.Value);
        Assert.Equal("SomeInt", eventAfterSomeIntAssigned.PropertyInfo.Name);
        var x = tracked.SomeInt;
        Assert.Equal(3, tracked.GetEvents().Count);
        var eventAfterSomeIntAccessed = tracked.GetEvents().Last();
        Assert.Equal(EventType.Get, eventAfterSomeIntAccessed.EventType);
        Assert.Equal(1, eventAfterSomeIntAccessed.Value);
        Assert.Equal("SomeInt", eventAfterSomeIntAccessed.PropertyInfo.Name);
    }

Hope this helps.

2018-02-13 10:49
by Darxtar
Ads