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?)
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);
}
}
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.
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
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.
If you are looking to do this at runtime, see the following Dr. Dobbs article Generating Code at Run Time With Reflection.Emit.
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.
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.