locked
Reflection - calling constructor with optional parameters using Type.Missing RRS feed

  • Question

  • User216 posted

    I'm currently playing with some Android.Dialog code - trying to create Dialogs from JSON.

    As part of this I'd like to invoke a constructor like:

        public StringElement(string caption = null, string value = null, string layoutName = null)
            : base(caption, value, layoutName ?? "dialog_multiline_labelfieldbelow")
        {
            Value = value;
        }
    

    The code I'm using looks like:

            var constructor = type.GetConstructors()
                                  .FirstOrDefault(c => c.GetParameters().All(p => p.IsOptional));
            if (constructor == null)
            {
                throw new ArgumentException("No parameterless Constructor found for " + key);
            }
            var parameters = constructor.GetParameters().Select(p => (object)Type.Missing).ToArray();
            var instance = constructor.Invoke(BindingFlags.OptionalParamBinding | BindingFlags.InvokeMethod | BindingFlags.CreateInstance, null, parameters, CultureInfo.InvariantCulture);
    

    This fails with System.ArgumentException: failed to convert parameters

    I can workaround this using:

        var parameters = constructor.GetParameters().Select(p => (object)p.DefaultValue).ToArray();
    

    But that code won't work on WP7...

    Is there some trick to using Type.Missing on MonoDroid? Is it possible?

    Stuart

    Friday, October 19, 2012 8:50 AM

All replies

  • User216 posted

    Or maybe I'm using Type.Missing wrong :)

    Friday, October 19, 2012 9:09 AM
  • User209 posted

    Seems ok to me! Type.Missing or Missing.Value should be used when trying to initialize a method with default values for the parameters. I tried your code and it seems to work fine when targeting WP7.1

    using System;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Windows;
    using Microsoft.Phone.Controls;
    
    namespace ReflectionTest
    {
        public partial class MainPage : PhoneApplicationPage
        {
            // Constructor
            public MainPage()
            {
                InitializeComponent();
    
                Loaded += OnLoaded;
            }
    
            private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
            {
                var type = typeof (StringElement);
    
                var constructor = type.GetConstructors()
                                  .FirstOrDefault(c => c.GetParameters().All(p => p.IsOptional));
    
                if (constructor == null)
                {
                    throw new ArgumentException("No parameterless Constructor found for " + type);
                }
    
                var parameters = constructor.GetParameters().Select(p => Type.Missing).ToArray();
    
                var instance = constructor.Invoke(BindingFlags.OptionalParamBinding | BindingFlags.InvokeMethod | BindingFlags.CreateInstance, null, parameters, CultureInfo.InvariantCulture);
    
                System.Diagnostics.Debug.WriteLine(instance);
            }
        }
    
        public class Element
        {
            public string Caption { get; set; }
            public string LayoutName { get; set; }
            public string Value { get; set; }
    
            public Element(string caption, string value, string layoutName)
            {
    
            }
    
            public override string ToString()
            {
                return string.Format("Caption: {0}, Value: {1}, LayoutName: {2}", Caption, Value, LayoutName);
            }
        }
    
        public class StringElement : Element
        {
            public StringElement(string caption = null, string value = null, string layoutName = null) 
                : base(caption, value, layoutName ?? "dialog_multiline_labelfieldbelow")
            {
                Value = value;
            }
        }
    }
    
    Friday, October 19, 2012 9:44 AM
  • User216 posted

    Sorry... I'm explaining myself badly...

    1 This works on MonoDroid but not completely on WP7 - because WP7 refection is broken and sets all DefaultValue properties to NULL regardless of what they are...

    var parameters = constructor.GetParameters().Select(p => (object)p.DefaultValue).ToArray();
    

    2 This works on WP7, but appears not to on MonoDroid

    var parameters = constructor.GetParameters().Select(p => (object)Type.Missing).ToArray();
    

    2 Is my preference - I'd love to use Type.Missing if I can.

    Or have I got myself confused? (Totally possible!)

    Coffee needed :)

    Stuart

    Friday, October 19, 2012 9:53 AM
  • User209 posted

    Hmm this is strange. Even p.DefaultValue does not work. I.e. if I set one of the string values to be something else than null in the StringElement constructor I still get null. Something seems really really broken, or we both have misunderstood something.

    Friday, October 19, 2012 10:31 AM
  • User216 posted

    Thanks Cheeseb - it's good to have company in my insanity.

    For WP7, it's a known limitation that DefaultValue doesn't work - I accept that, I know I can't change it and I've moved on :) (and breathe...)

    For Droid, I think DefaultValue does work - but I can't seem to get Type.Missing to work... and I'm not sure if I've just messed it up somehow...

    Or are you saying Type.Missing isn't working on WP7 either... in which case... arrrgggghhh...

    I suspect I'm going to have to go #if at some point... but I'm hoping I don't...

    Friday, October 19, 2012 10:40 AM
  • User209 posted

    I only tested this on WP7 and neither seems to work for me. I.e. if I change the constructor of StringElement from:

    public StringElement(string caption = null, string value = null, string layoutName = null)
            : base(caption, value, layoutName ?? "dialog_multiline_labelfieldbelow")
    

    to:

    public StringElement(string caption = "bingabong", string value = null, string layoutName = null)
            : base(caption, value, layoutName ?? "dialog_multiline_labelfieldbelow")
    

    Initializing it with p.DefaultValue or Type.Missing just results in null for caption no matter what. However I might be doing something wrong (I am aware of the missing property assignments in Element, which I have corrected).

    Friday, October 19, 2012 10:45 AM
  • User216 posted

    Ah....

    In that case I've misremembered how WP7's hacky DefaultValue reflection works...

    The DefaultValue being missing is a known and documented limitation - I've hit it several times before.

    So I'm going to have to branch the code anyways...

    So while I'd still quite like to know if I can use Type.Missing on MonoDroid (am I just doing something wrong?) - it's definitely not uber-important...

    Serves me right for using too much reflection :)

    Friday, October 19, 2012 11:04 AM
  • User216 posted

    The MSDN ref for DefaultValue on WP7 is: http://msdn.microsoft.com/en-us/library/system.reflection.parameterinfo.defaultvalue%28v=vs.95%29.aspx

    Interestingly, DefaultValue is supported in the Portable Class Libraries - but I bet it doesn't work!

    Friday, October 19, 2012 11:06 AM
  • User48 posted

    This is a mono bug. Mono, including desktop mono, doesn't support using Type.Missing in this fashion.

    Friday, October 19, 2012 8:31 PM
  • User216 posted

    Thanks Jon

    Saturday, October 20, 2012 1:46 PM
  • User468 posted

    It looks like the Type.Missing bug has been fixed. When will the fix make it's way to the MonoDroid runtime? I'm experiencing the same bug with the latest version of monodroid.

    Friday, April 12, 2013 1:57 AM
  • User48 posted

    @SergioDeAlbuquerque: Please try the 4.7.x alpha releases. Those are based on the Mono 3.0 runtime, which would (should) have the fix (if the bug has actually been fixed).

    Friday, April 12, 2013 6:34 PM