ekonbenefits / impromptu-interface

Static interface to dynamic implementation (duck casting). Uses the DLR combined with Reflect.Emit.
Apache License 2.0
655 stars 67 forks source link

TryGetMember parameter GetMemberBinder's ReturnType always object #BUG #14

Closed coder-zzo closed 9 years ago

coder-zzo commented 9 years ago

This's my usage:

public interface IMailClientConfig { string Host { get; } int SmtpPort { get; } }

    private class ConfigImpl : DynamicObject
    {
        private readonly Dictionary<string, string> _section;

        public ConfigImpl(string section)
        {
            _section = Sections[section];
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
             result = Convert.ChangeType(_section[binder.Name], binder.ReturnType);
            //binder.ReturnType always object , in SmtpPort should int , in Host  should string.

            return true;
        }
    }

IMailClientConfig r = new ConfigImpl("MailClient").ActLike(); var str = r.Host; // Working fine. var num=r.SmtpPort; // Throw RuntimeBinderException. Can't cast string to int, see above

Now i only can make a 'patch' , but that's isn't beautiful coding.

private class ConfigImpl : DynamicObject { private readonly Dictionary<string, string> _section; private readonly Dictionary<string, Type> _props;

        public ConfigImpl(string section)
        {
            _section = Sections[section];
            _props = typeof(T).GetProperties().ToDictionary(p => p.Name, p => p.PropertyType);
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (_section.ContainsKey(binder.Name) == false)
                throw new ArgumentException("Config not found");

            result = Convert.ChangeType(_section[binder.Name], binder.ReturnType);// _props[binder.Name]);
            return true;
        }
    }
jbtule commented 9 years ago

The ReturnType on the binder is always going to be object. It's how the DLR works with C#. But there are two ways to go about this in your case.

Using a Dynamitey.DynamicObjects.BaseObject https://github.com/ekonbenefits/dynamitey/blob/master/Dynamitey/DynamicObjects/BaseObject.cs

Then you can call TryTypeForName(string binderName, out Type type) from inside TryGetMember and get the return type.

OR

You can write another DynamicObject to wrap the result of TryGetMember with TryConvert overridden.

  public override bool TryConvert(System.Dynamic.ConvertBinder binder, out object result)
  {
     result = Convert.ChangeType(_target, binder.Type)
     return true;
  }

The reason for this is that the DLR for c# does this in two passes, invokes the method as if it returns object and then invokes the cast to the intended type dynamically.

coder-zzo commented 9 years ago

Thanks for your help, I used second way. Although added a Class, but the code looks better than original.

    private class ConfigImpl<T> : DynamicObject
    {
        private class AutoConvertSupport : DynamicObject
        {
            private readonly object _value;

            public AutoConvertSupport(object value)
            {
                _value = value;
            }

            public override bool TryConvert(ConvertBinder binder, out object result)
            {
                result = Convert.ChangeType(_value, binder.Type);
                return true;
            }
        }

        private readonly Dictionary<string, string> _section;

        public ConfigImpl(string section)
        {
            _section = Sections[section];
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (_section.ContainsKey(binder.Name) == false)
                throw new ArgumentException("Config not found");

            result = new AutoConvertSupport(_section[binder.Name]);
            return true;
        }
    }