12

I'm using Type.GetConstructor(Type.EmptyTypes) to get the default constructor for a class. It works if the class has a default constructor with no parameters (class A). But it doesn't work if a class has a constructor with all parameters optional (class B). Program doesn't know what the optional parameters are because it only needs the default constructor. What can statements can I use to make it work for both cases? Thanks, appreciate any help!

public class A
{
    public A() {}
} 

public class B
{
    public B(int i = 0, string str = "") {}
}
nawfal
  • 70,104
  • 56
  • 326
  • 368
dlsou
  • 635
  • 2
  • 8
  • 18
  • Thanks for all the responses, they're very informative. Since these are automatically generated classes, I just changed it to always include a constructor with no parameter. – dlsou Apr 08 '11 at 12:14
  • Also note that your class `B` cannot be used in generic for a type parameter `T` if `T` has the constraint `where T : new()`. The public instance constructor is not considered parameterless just because all its parameters are optional. – Jeppe Stig Nielsen Feb 27 '14 at 08:01

6 Answers6

7

Say I have the following class:

public class SomeClass
{
    public SomeClass()
    {

    }

    public SomeClass(int x)
    {
    }

    public SomeClass(int x = 0, int y = 0)
    {

    }
}

Basically, you're asking for a query that will find the constructors that match constructor 1 and 3 above? If so, use this:

var constuctors = typeof(SomeClass).GetConstructors()
            .Where(x => x.GetParameters().Count() == 0 
                    ||  x.GetParameters().Count(param => param.GetCustomAttributes(typeof(OptionalAttribute), false).Count() > 0) == x.GetParameters().Count());    

Incredibly nasty query, but it gets the job done returning only 1 and 3 above.

Tejs
  • 40,736
  • 10
  • 68
  • 86
4

The problem is that the C# compiler produces this:

public class B
{
    // Methods
    public B([Optional, DefaultParameterValue(0)] int i, [Optional, DefaultParameterValue("")] string str)
    {
    }
}

Something like below should work:

public static class TypeHelper {
    public static ConstructorInfo GetDefaultConstructor<TType>() {
        var type = typeof(TType);
        return type.GetDefaultConstructor();
    }

    public static ConstructorInfo GetDefaultConstructor(this Type type) {
        if(type == null) throw new ArgumentNullException("type");
        var constructor = type.GetConstructor(Type.EmptyTypes);
        if(constructor == null) {
            var ctors = 
                from ctor in type.GetConstructors()
                let prms = ctor.GetParameters()
                where prms.All(p=>p.IsOptional)
                orderby prms.Length
                select ctor;                        
            constructor = ctors.FirstOrDefault();
        }
        return constructor;
    }
}
Damian
  • 2,709
  • 2
  • 29
  • 40
  • 1
    You could write a LINQ statement to find all the constructor parameters with both optional and DefaultParameterValue specified. – Damian Apr 07 '11 at 19:09
  • Or just Optional; they're probably never used on their own. – Jouke van der Maas Apr 07 '11 at 19:12
  • Nice answer, but the firstordefault approach is not appropriate. You're deciding the operator overloading for the user instead of C#. I mean there can be cases of compiler ambiguity about overload resolution, but here you're deciding one for him :) – nawfal Apr 24 '13 at 04:42
  • Good point if a `B(string i=null, string y=null)` overload was added we would not get overload resolution based on the language rules, but you could add Type hints to GetDefaultConstructor to closer match that. – Damian Apr 24 '13 at 12:20
3

The problem is that optional parameters are nothing more than a compile time concept. You'll need to specify the constructor completely.

var ci = typeof(B).GetConstructor(new [] { typeof(int), typeof(string) });

You can write a help function that will invoke the constructor with the default values though. My example is not as robust as it should be but it should get you started.

static Func<T> CreateDefaultConstructor<T>(ConstructorInfo ci)
{
    var l = new List<object>();
    foreach (var p in ci.GetParameters())
    {
        if (p.IsOptional)
        {
            l.Add(p.RawDefaultValue);
        }
    }
    return () => (T)ci.Invoke(l.ToArray());
}
ChaosPandion
  • 77,506
  • 18
  • 119
  • 157
2

The problem is that, in the case of B, it does not have a constructor with no parameters.

Optional arguments are a compile time construct - in the IL, it's a constructor with 2 parameters (which are flagged with attributes). As such, there is no default constructor as far as Reflection is concerned.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
1

To get the one that has more optional parameters or an empty constructor at all, use:

typeof(myClass)
.GetConstructors()
.OrderBy(x => x.GetParameters().Length - x.GetParameters().Count(p => p.IsOptional))
.FirstOrDefault();
jrivam
  • 1,081
  • 7
  • 12
0

When a constructor, or any other method, has optional arguments it doesn't cause the compiler to generate multiple versions of the method. Instead it generates a single method which has all of the specified parameters. The default values are encoded in attributes attached to the method signature. These are used at the call site to make their values optional.

So here there is no default constructor but instead a single one with 2 parameters

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454