Using reflection to set List<dynamic> to List<T> - c#

Say, I have these properties in a class :
public class Example
{
public List<Toyota> listToyota;
public List<Honda> listHonda;
public List<Huyndai> listHuyndai;
...
...
}
And there will be more properties if there are new car brands. Each brand is a table.
Normally, I would do this to get the data from tables :
Example result = new Example();
switch (brandname)
{
case "Toyota":
result.listToyota = * select table from context * ;
break;
case "Honda":
result.listHonda = * select table from context * ;
break;
...
}
Also, I'll have to add more code when there are new brands. I found this very annoying/time-consuming and decided to switch to a dynamic approach. I've sucessfully get the tables dynamically :
tblName = "Toyota";
IEnumerable<dynamic> table = typeof(MyContext).GetProperty(tblName).GetValue(context, null) as IEnumerable<dynamic>;
But I failed to dynamically set the property value, in this example, is listToyota :
query = (from a in table select a).ToList() as List<dynamic>;
SetPropertyValue(result, "listToyota", query);
I got this error :
Object of type 'System.Collections.Generic.List1[System.Object]'
cannot be converted to type
'System.Collections.Generic.List1[Toyota]'.
SetPropertyValue is a very simple function using System.Reflection :
static void SetPropertyValue(object p, string propName, object value)
{
Type t = p.GetType();
System.Reflection.PropertyInfo info = t.GetProperty(propName);
if (info == null)
return;
if (!info.CanWrite)
return;
info.SetValue(p, value, null);
}
Any advices are greatly appreciated!

Please try something like this
static void SetPropertyValue(object p, string propName, object value)
{
Type t = p.GetType();
System.Reflection.PropertyInfo info = t.GetProperty(propName);
if (info == null)
return;
if (!info.CanWrite)
return;
var elemtype = info.PropertyType.GetElementType();
var castmethod = typeof(Enumerable).GetMethod("Cast", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(elemtype);
var tolist = typeof(Enumerable).GetMethod("ToList", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(elemtype);
var collection = castmethod.Invoke(null, new object[] { value });
var list = tolist.Invoke(null, new object[] { collection });
info.SetValue(p, list, null);
}

I think the main problem is your software design. I wouldn't use dynamic this way.
I would create a Brand-Class. If you want to use dynamic, try something like this:
public class DynamicBrand : DynamicObject
{
private IDictionary<string, object> myDynamicValues;
public DynamicBrand()
: base()
{
myDynamicValues = new Dictionary<string, object>();
}
public void AddMember(string Name, object Value)
{
if (!myDynamicValues.ContainsKey(Name.Trim().ToLower()))
{
myDynamicValues.Add(Name.ToLower().Trim(), Value);
}
else
{
throw new Exception("The Member with the Name: " + Name + " already exists!");
}
}
public override bool TrySetMember(SetMemberBinder Binder, object Value)
{
if (myDynamicValues.ContainsKey(Binder.Name.ToLower()))
{
myDynamicValues[Binder.Name.ToLower()] = Value;
return true;
}
else
{
myDynamicValues.Add(Binder.Name.ToLower(), Value);
}
return true;
}
publc override bool TryGetMember(GetMemberBinder Binder, out object Result)
{
if (myDynamicValues.ContainsKey(Binder.Name.ToLower()))
{
Result = myDynamicValues[Binder.Name.ToLower()];
return true;
}
else
{
Result = null;
return false;
}
}
I would change your example class
public class Example
{
// string = brand-name; list = list of dynamic brand items
public Dictionary<string, List<DynamicBrand>> brands;
}
Whenyou fill your data, just add a new dynamic brand to your brands-list and simply add the member you need.
EDIT: Dynamic isn't a very nice solution for your problem. I would think about a completly different structure.

Related

Figure out which properties accessed on expando object

I use a template engine that renders templates from c# objects (nested). I would like to reflect and figure out which properties / objects are used in each template string.
An ideal way would be to build a "dummy" object representing the right shape and render this in the template. I would then inspect this object afterwards to find out which properties were accessed. This would allow me to keep this logic independant of the template library.
Any idea how i might implement this? The expando object is built dynamically like this:
var dynamicObject = new ExpandoObject() as IDictionary<string, Object>;
foreach (var property in properties) {
dynamicObject.Add(property.Key,property.Value);
}
Had some ideas along these lines:
public class DummyObject {
public DummyObject() {
Accessed = new Dictionary<string, bool>();
}
public Dictionary<string, bool> Accessed;
object MyProp {
get {
Accessed["MyProp"] = true;
return "";
}
}
}
But this custom property obviously doesn't work with the dictionary / expando object. Any ideas of a route forward here?
You can override the TryGetMember method on DynamicObject:
public sealed class LoggedPropertyAccess : DynamicObject {
public readonly HashSet<string> accessedPropertyNames = new HashSet<string>();
public override bool TryGetMember(GetMemberBinder binder, out object result) {
accessedPropertyNames.Add(binder.Name);
result = "";
return true;
}
}
and then the following will output the accessed property names
dynamic testObject = new LoggedPropertyAccess();
string firstname = testObject.FirstName;
string lastname = testObject.LastName;
foreach (var propertyName in testObject.accessedPropertyNames) {
Console.WriteLine(propertyName);
}
Console.ReadKey();
N.B. There is still an issue here -- this works only as long as the template library expects only strings from the properties. The following code will fail, because every property will return a string:
DateTime dob = testObject.DOB;
In order to resolve this, and also allow for nested objects, have TryGetMember return a new instance of LoggedPropertyAccess. Then, you can override the TryConvert method as well; where you can return different values based on the conversion to different types (complete code):
using System;
using System.Collections.Generic;
using System.Dynamic;
namespace DynamicObjectGetterOverride {
public sealed class LoggedPropertyAccess : DynamicObject {
public readonly Dictionary<string, object> __Properties = new Dictionary<string, object>();
public readonly HashSet<string> __AccessedProperties = new HashSet<string>();
public override bool TryGetMember(GetMemberBinder binder, out object result) {
if (!__Properties.TryGetValue(binder.Name, out result)) {
var ret = new LoggedPropertyAccess();
__Properties[binder.Name] = ret;
result = ret;
}
__AccessedProperties.Add(binder.Name);
return true;
}
//this allows for setting values which aren't instances of LoggedPropertyAccess
public override bool TrySetMember(SetMemberBinder binder, object value) {
__Properties[binder.Name] = value;
return true;
}
private static Dictionary<Type, Func<object>> typeActions = new Dictionary<Type, Func<object>>() {
{typeof(string), () => "dummy string" },
{typeof(int), () => 42 },
{typeof(DateTime), () => DateTime.Today }
};
public override bool TryConvert(ConvertBinder binder, out object result) {
if (typeActions.TryGetValue(binder.Type, out var action)) {
result = action();
return true;
}
return base.TryConvert(binder, out result);
}
}
}
and use as follows:
using System;
using static System.Console;
namespace DynamicObjectGetterOverride {
class Program {
static void Main(string[] args) {
dynamic testObject = new LoggedPropertyAccess();
DateTime dob = testObject.DOB;
string firstname = testObject.FirstName;
string lastname = testObject.LastName;
dynamic address = testObject.Address;
address.House = "123";
address.Street = "AnyStreet";
address.City = "Anytown";
address.State = "ST";
address.Country = "USA";
WriteLine("----- Writes the returned values from reading the properties");
WriteLine(new { firstname, lastname, dob });
WriteLine();
WriteLine("----- Writes the actual values of each property");
foreach (var kvp in testObject.__Properties) {
WriteLine($"{kvp.Key} = {kvp.Value}");
}
WriteLine();
WriteLine("----- Writes the actual values of a nested object");
foreach (var kvp in testObject.Address.__Properties) {
WriteLine($"{kvp.Key} = {kvp.Value}");
}
WriteLine();
WriteLine("----- Writes the names of the accessed properties");
foreach (var propertyName in testObject.__AccessedProperties) {
WriteLine(propertyName);
}
ReadKey();
}
}
}

Serializing linked objects

I try to create an object model for the following problem.
I need a folder object (comparable to directory folders). Each folder can contain additional sub folders and in addition parameter objects (comparable to files). In addition, each parameter needs to know in which folder it resides. This is easy so far. So I implemented the following working solution.
I have a base object, that can either be inherited to a folder or a parameter:
[Serializable()]
public class Entry
{
public Func<string> GetPath;
public string Path
{
get
{
if (GetPath == null) return string.Empty;
return GetPath.Invoke();
}
}
}
Now I created a FolderEntry, that inherits from Entry and supports adding new sub entries by implementing IList<>.
[Serializable()]
class FolderEntry : Entry, IList<Entry>
{
private readonly List<Entry> _entries;
public FolderEntry()
{
_entries = new List<Entry>();
}
public string FolderName { get; set; }
private void SetPathDelegate(Entry entry)
{
if (entry.GetPath != null) throw new ArgumentException("entry already assigned");
entry.GetPath = () =>
{
if (GetPath == null || string.IsNullOrEmpty(GetPath.Invoke())) return FolderName;
return GetPath.Invoke() + "|" + FolderName;
};
}
public void Add(Entry item)
{
SetPathDelegate(item);
_entries.Add(item);
}
[...]
}
To support Undo/Redo functionality, I made all classes serializable by adding the Serializable-Attribute.
This serialization is working so far using the following test:
var folderA = new FolderEntry();
var folderB = new FolderEntry();
folderA.Add(folderB);
var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
var memStream = new System.IO.MemoryStream();
serializer.Serialize(memStream, folderA);
Now here’s my problem. There is in addition the need that each parameter knows its index inside the hosting list. I changed my Entry-object to have a property Index and a delegate GetIndex in the same manner as Path and GetPath before:
[Serializable()]
public class Entry
{
public Func<string> GetPath;
public string Path
{
get
{
if (GetPath == null) return string.Empty;
return GetPath.Invoke();
}
}
public Func<int> GetIndex;
public int Index
{
get
{
if (GetIndex == null) return -1;
return GetIndex.Invoke();
}
}
}
Inside the SetPathDelegate of the Folder-object I assigned the new delegate
private void SetPathDelegate(Entry entry)
{
if (entry.GetPath != null) throw new ArgumentException("entry already assigned");
if (entry.GetIndex != null) throw new ArgumentException("entry already assigned");
entry.GetPath = () =>
{
if (GetPath == null || string.IsNullOrEmpty(GetPath.Invoke())) return FolderName;
return GetPath.Invoke() + "|" + FolderName;
};
entry.GetIndex = () =>
{
return _entries.IndexOf(entry);
};
}
If I try to serialize this, I get an expection that my „FolderEntry+<>c__DisplayClass2“ in Assembly… is not marked as serializable. I can’t see an obvious difference between GetPath and GetIndex. To narrow it down, I replaced content of the created GetIndex delegate in SetPathDelegate from
entry.GetIndex = () =>
{
return _entries.IndexOf(entry);
};
To
entry.GetIndex = () =>
{
return -1;
};
To my astonishment this is serializable again. Why doesn‘t cause my GetPath delegate any problems regarding the serialization but my GetIndex delegate does?
The problem is the anonymous function that you assign to GetIndex. At runtime, a new type is created, which is not marked as serializable.
According to this post, you should set a SurrogateSelector for the formatter (with some caveats, read the article in detail):
formatter.SurrogateSelector = new UnattributedTypeSurrogateSelector();
I'me pasting here the classes from the article, for future reference and in order to make the answer thorough.
public class UnattributedTypeSurrogate : ISerializationSurrogate
{
private const BindingFlags publicOrNonPublicInstanceFields =
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
public void GetObjectData(object obj,
SerializationInfo info, StreamingContext context)
{
var type = obj.GetType();
foreach (var field in type.GetFields(publicOrNonPublicInstanceFields))
{
var fieldValue = field.GetValue(obj);
var fieldValueIsNull = fieldValue != null;
if (fieldValueIsNull)
{
var fieldValueRuntimeType = fieldValue.GetType();
info.AddValue(field.Name + "RuntimeType",
fieldValueRuntimeType.AssemblyQualifiedName);
}
info.AddValue(field.Name + "ValueIsNull", fieldValueIsNull);
info.AddValue(field.Name, fieldValue);
}
}
public object SetObjectData(object obj,
SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
var type = obj.GetType();
foreach (var field in type.GetFields(publicOrNonPublicInstanceFields))
{
var fieldValueIsSerializable = info.GetBoolean(field.Name + "ValueIsNull");
if (fieldValueIsSerializable)
{
var fieldValueRuntimeType = info.GetString(field.Name + "RuntimeType");
field.SetValue(obj,
info.GetValue(field.Name, Type.GetType(fieldValueRuntimeType)));
}
}
return obj;
}
}
public class UnattributedTypeSurrogateSelector : ISurrogateSelector
{
private readonly SurrogateSelector innerSelector = new SurrogateSelector();
private readonly Type iFormatter = typeof(IFormatter);
public void ChainSelector(ISurrogateSelector selector)
{
innerSelector.ChainSelector(selector);
}
public ISerializationSurrogate GetSurrogate(
Type type, StreamingContext context, out ISurrogateSelector selector)
{
if (!type.IsSerializable)
{
selector = this;
return new UnattributedTypeSurrogate();
}
return innerSelector.GetSurrogate(type, context, out selector);
}
public ISurrogateSelector GetNextSelector()
{
return innerSelector.GetNextSelector();
}
}

Generic way to set property values with .NET

I have hundreds of properties that need to be set in an object, which is hierarchical by nature and would like a generic way of doing so, is this feasible? One of the driving reasons is for error checking and logging.
In this sample below I am setting ReferenceModelIdentifier to a string
public override void Execute(MESSAGE message)
{
message.MISMOReferenceModelIdentifier= "3.0.0.263.12";
Tasks.Add(new AboutVersionsTask(LoanNumber, LoanState));
Tasks.Add(new DealSetsTask(LoanNumber, LoanState));
foreach (var task in Tasks)
using (task)
task.Execute(message);
// Were the tasks successful?
Debug.Assert(message.ABOUT_VERSIONS.ABOUT_VERSION.Count > 0, "ABOUT_VERSION");
Debug.Assert(message.DEAL_SETS.DEAL_SET.Count > 0, "DEAL_SET");
Log.Info("Finished Execute");
}
and in this sample I am setting ApplicationReceivedDate to another object of type MISMODate
public override void Execute(MESSAGE message)
{
var node = new LOAN_DETAIL();
var con = GetOpenConnection();
string sql;
IEnumerable<dynamic> data;
dynamic first;
if (LoanState == LoanStateEnum.AtClosing)
{
//(224) ApplicationReceivedDate
sql = #"
SELECT date ApplicationReceivedDate
FROM foo (nolock)
WHERE loannum = #loannum";
data = con.Query<dynamic>(sql, new { loannum = new DbString { Value = LoanNumber, IsFixedLength = true, Length = 15, IsAnsi = true } });
first = data.First();
node.ApplicationReceivedDate = new MISMODate { TypedValue = first.ApplicationReceivedDate.ToString("yyyy-MM-dd") };
}
}
What I started out coding was something along the lines of
protected void SetValue(Object property, Object value)
{
}
and the usage would be
SetValue(message.MISMOReferenceModelIdentifier, "3.0.0.263.12");
EDIT
What I ended up doing was this
protected void SetValue(object theObject, string theProperty, object theValue)
{
try
{
var msgInfo = theObject.GetType().GetProperty(theProperty);
msgInfo.SetValue(theObject, theValue, null);
}
catch (Exception e)
{
Log(e);
}
}
and usage would be
SetValue(message, "MISMOReferenceModelIdentifier", "3.0.0.263.12");
Thank you,
Stephen
You can iterate over your object graph and set property values with reflection:
object obj; // your object
Type t = obj.GetType();
foreach (var propInfo in t.GetProperties())
{
propInfo.SetValue(obj, value, null);
}
If you can ensure that your class properties have getters you can iterate your object graph recursive:
public static void setValsRecursive(object obj, object value)
{
Type t = obj.GetType();
foreach (var propInfo in t.GetProperties())
{
if (propInfo.PropertyType.IsClass)
{
object propVal = propInfo.GetValue(obj, null);
setValsRecursive(propVal, value);
}
propInfo.SetValue(obj, value, null);
}
}
This is a dumb function that sets every property to the same value ...
You can use PropertyInfo to dynamically set values in a generic way.

C# dynamic classes

I'm talking about something similar to dynamic. This didn't answer my question, hence this question. I want to have a class that I can add properties to at runtime. It needs to be inherited from the type object.
I've seen inheriting from DynamicObject, but it didn't state how to add properties at run-time. Could some light be shed on this for me pls?
I have a class like this:
public class SomeModel : DynamicObject {
public string SomeMandatoryProperty {get; set;}
}
I'd like to add all properties from another class to this class at runtime. So eg.
SomeModel m = new SomeModel();
m = someOtherClass;
string hi = m.otherClassProp; //Property from other class is added.
string mandatory = m.SomeMandatoryProperty; //From the mandatory property set previously.
I think you are looking for ExpandoObject:
The ExpandoObject class enables you to
add and delete members of its
instances at run time and also to set
and get values of these members. This
class supports dynamic binding, which
enables you to use standard syntax
like sampleObject.sampleMember instead
of more complex syntax like
sampleObject.GetAttribute("sampleMember").
dynamic manager;
manager = new ExpandoObject();
manager.Name = "Allison Brown";
manager.Age = 42;
manager.TeamSize = 10;
You should be able to make use of ExpandoObject instead. An ExpandoObject can have members added or removed at runtime and has very nice support if you want to convert to XML etc.
From MSDN Documentation:
dynamic employee, manager;
employee = new ExpandoObject();
employee.Name = "John Smith";
employee.Age = 33;
manager = new ExpandoObject();
manager.Name = "Allison Brown";
manager.Age = 42;
manager.TeamSize = 10;
You'd want to use the ExpandoObject as you can dynamically add properties as needed. There isn't however a direct way to populate an instance with the values from another object easily. You'll have to add it manually using reflection.
Do you want to write a wrapper object where you could add properties to while still accessing the inner? You may want to consider it that way you don't have to manage two copies of values between two different object instances. I wrote a test class to wrap string objects to demonstrate how you can do this (similar to how the ExpandoObject works). It should give you an idea on how you can do this for your types.
class DynamicString : DynamicObject
{
static readonly Type strType = typeof(string);
private string instance;
private Dictionary<string, object> dynProperties;
public DynamicString(string instance)
{
this.instance = instance;
dynProperties = new Dictionary<string, object>();
}
public string GetPrefixString(string prefix)
{
return String.Concat(prefix, instance);
}
public string GetSuffixString(string suffix)
{
return String.Concat(instance, suffix);
}
public override string ToString()
{
return instance;
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
if (binder.Type != typeof(string))
return base.TryConvert(binder, out result);
result = instance;
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
var method = strType.GetMethod(binder.Name, args.Select(a => a.GetType()).ToArray());
if (method == null)
{
result = null;
return false;
}
result = method.Invoke(instance, args);
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var members = strType.GetMember(binder.Name);
if (members.Length > 0)
{
var member = members.Single();
switch (member.MemberType)
{
case MemberTypes.Property:
result = ((PropertyInfo)member).GetValue(instance, null);
return true;
break;
case MemberTypes.Field:
result = ((FieldInfo)member).GetValue(instance);
return true;
break;
}
}
return dynProperties.TryGetValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
var ret = base.TrySetMember(binder, value);
if (ret) return true;
dynProperties[binder.Name] = value;
return true;
}
}
If you want to be adventurous, you can define your own meta objects to handle the bindings. You could end up with reusable meta objects for different types and simplify your code immensely. I've been playing with this for a while and have this so far. It doesn't handle dynamically adding properties yet. I won't be working on this any further but I'll just leave it here for reference.
class DynamicString : DynamicObject
{
class DynamicStringMetaObject : DynamicMetaObject
{
public DynamicStringMetaObject(Expression parameter, object value)
: base(parameter, BindingRestrictions.Empty, value)
{
}
public override DynamicMetaObject BindConvert(ConvertBinder binder)
{
if (binder.Type == typeof(string))
{
var valueType = Value.GetType();
return new DynamicMetaObject(
Expression.MakeMemberAccess(
Expression.Convert(Expression, valueType),
valueType.GetProperty("Instance")),
BindingRestrictions.GetTypeRestriction(Expression, valueType));
}
return base.BindConvert(binder);
}
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
System.Diagnostics.Trace.WriteLine(String.Format("BindGetMember: {0}", binder.Name));
var valueType = Value.GetType();
var self = Expression.Convert(Expression, valueType);
var valueMembers = valueType.GetMember(binder.Name);
if (valueMembers.Length > 0)
{
return BindGetMember(self, valueMembers.Single());
}
var members = typeof(string).GetMember(binder.Name);
if (members.Length > 0)
{
var instance =
Expression.MakeMemberAccess(
self,
valueType.GetProperty("Instance"));
return BindGetMember(instance, members.Single());
}
return base.BindGetMember(binder);
}
private DynamicMetaObject BindGetMember(Expression instance, MemberInfo member)
{
return new DynamicMetaObject(
Expression.Convert(
Expression.MakeMemberAccess(instance, member),
typeof(object)),
BindingRestrictions.GetTypeRestriction(Expression, Value.GetType())
);
}
}
public string Instance { get; private set; }
public DynamicString(string instance)
{
Instance = instance;
}
public override DynamicMetaObject GetMetaObject(Expression parameter)
{
return new DynamicStringMetaObject(parameter, this);
}
public override string ToString()
{
return Instance;
}
public string GetPrefixString(string prefix)
{
return String.Concat(prefix, Instance);
}
public string GetSuffixString(string suffix)
{
return String.Concat(Instance, suffix);
}
}

dynamic keyword enables "maybe" monad?

So I have this in my C# lib:
public static TOut IfNotNull<TIn, TOut>
(this TIn instance, Func<TIn, TOut> func)
{
return instance == null ? default(TOut) : func(instance);
}
Used like:
DateTime? expiration = promo.IfNotNull(p => p.TermsAndConditions.Expiration)
.IfNotNull(e => e.Date);
I keep wracking my brain trying to figure out how to use the C# 4 dynamic keyword to enable this syntax instead:
DateTime? expiration = promoOffer.TermsAndConditions.Maybe()
.Expiration.Maybe()
.Date;
I had a couple examples that I thought worked but they broke down when you start chaining the Maybe()s.
Any ideas?
(Am I wasting my time? Is Maybe() a win over IfNotNull())?
I don't think that using dynamic type is a good idea here, because the syntax isn't going to look much better and you sacrifice the type safety (and IntelliSense) by using dynamic typing.
However, here is an example of what you can do. The idea is that you wrap objects into DynamicWrapper (your monadic value :-)) that can either contain a null value or an actual value. It would inherit from DynamicObject and delegate all calls to the actual object (if there is any) or immediately return null (that would be monadic bind):
class DynamicWrapper : DynamicObject {
public object Object { get; private set; }
public DynamicWrapper(object o) { Object = o; }
public override bool TryGetMember(GetMemberBinder binder, out object result) {
// Special case to be used at the end to get the actual value
if (binder.Name == "Value") result = Object;
// Binding on 'null' value - return 'null'
else if (Object == null) result = new DynamicWrapper(null);
else {
// Binding on some value - delegate to the underlying object
var getMeth = Object.GetType().GetProperty(binder.Name).GetGetMethod();
result = new DynamicWrapper(getMeth.Invoke(Object, new object[0]));
return true;
}
public static dynamic Wrap(object o) {
return new DynamicWrapper(o);
}
}
The example supports only properties and it uses reflection in a pretty inefficient way (I think it could be optimized using DLR). Here is an example how it works:
class Product {
public Product Another { get; set; }
public string Name { get; set; }
}
var p1 = new Product { Another = null };
var p2 = new Product { Another = new Product { Name = "Foo" } };
var p3 = (Product)null;
// prints '' for p1 and p3 (null value) and 'Foo' for p2 (actual value)
string name = DynamicWrapper.Wrap(p1).Another.Name.Value;
Console.WriteLine(name);
Note that you can chain the calls freely - there is only something special at the beginning (Wrap) and at the end (Value), but in the middle, you can write .Another.Another.Another... as many times you want.
This solution is similar to Tomas' except that it uses CallSite to invoke properties on the target instance and also supports casting and extra calls to Maybe (as per your example).
public static dynamic Maybe(this object target)
{
return new MaybeObject(target);
}
private class MaybeObject : DynamicObject
{
private readonly object _target;
public MaybeObject(object target)
{
_target = target;
}
public override bool TryGetMember(GetMemberBinder binder,
out object result)
{
result = _target != null ? Execute<object>(binder).Maybe() : this;
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args, out object result)
{
if (binder.Name == "Maybe" &&
binder.ReturnType == typeof (object) &&
binder.CallInfo.ArgumentCount == 0)
{
// skip extra calls to Maybe
result = this;
return true;
}
return base.TryInvokeMember(binder, args, out result);
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
if (_target != null)
{
// call Execute with an appropriate return type
result = GetType()
.GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.Instance)
.MakeGenericMethod(binder.ReturnType)
.Invoke(this, new object[] {binder});
}
else
{
result = null;
}
return true;
}
private object Execute<T>(CallSiteBinder binder)
{
var site = CallSite<Func<CallSite, object, T>>.Create(binder);
return site.Target(site, _target);
}
}
The following code should demonstrate it in use:
var promoOffer = new PromoOffer();
var expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date;
Debug.Assert((DateTime?) expDate == null);
promoOffer.TermsAndConditions = new TermsAndConditions();
expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date;
Debug.Assert((DateTime?) expDate == null);
promoOffer.TermsAndConditions.Expiration = new Expiration();
expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date;
Debug.Assert((DateTime?) expDate == null);
promoOffer.TermsAndConditions.Expiration.Date = DateTime.Now;
expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date;
Debug.Assert((DateTime?) expDate != null);

Categories

Resources