1

In C# can I pass a delegate an argument list for a class constructor in a fairly concise way?

I know this is extremely situational and may seem a bit of an odd request, also it's not for the factory pattern. If it helps think of it as a challenge. The correct answer to this question may be that it is not possible.

Func<MyClassConstructionArgsType, MyClass> make_MyClass =
    args => {return new MyClass(args);};

var n = make_MyClass(comma separated arguments);

I also need there to not be a copy of the description of what the arguments are, the below for example is not a solution:

Func<int, string, MyClass> make_MyClass =
    (a, b) => {return new MyClass(a, b);};

Or, for the same reasons this:

Class Args
{
    ...
}

Func<Args, MyClass> make_MyClass =
    a => {return new MyClass(a);};

var n = make_MyClass(Args(args));

Dito where this is the case:

var n = make_MyClass<MyClass>(comma separated arguments);

The Object[]{comma separated arguments} approach is good except that optional parameters also need to be supported.

This question was created as a result of Anastasiosyal's answer from the following question: c# class reference as opposed to instance reference

Josh first answer seems as close as is possible in C#.

Community
  • 1
  • 1
alan2here
  • 3,223
  • 6
  • 37
  • 62
  • 2
    Anon downvoter please use the comments, answer or edit first. – alan2here Feb 20 '12 at 19:00
  • I didn't down vote, but see [this post for more info](http://meta.stackexchange.com/questions/121350/ive-just-been-down-voted-how-should-i-react-to-this) – Andrew Barber Feb 20 '12 at 19:03
  • What you are asking for isn't particularly clear. Can you put an example of how you'd *like* it to work, and maybe we can start from there? Also, the root problem you are trying to solve? I assume this is related to the nasty class nesting in your last question. I would certainly have answered with one of the latter two examples that you gave. Why are they not appropriate? – Chris Shain Feb 20 '12 at 19:05
  • The first example shows how I would like it to work, after "make_MyClass" has been defined then "var n = make_MyClass(comma separated arguments);" can be used, for example it could be "var n = make_MyClass(4, "abc");", but with the additional requirements listed in the post reguarding not repeating information about the arguments in the delegate declaration. – alan2here Feb 20 '12 at 19:08
  • 3
    Sounds to me you're trying too hard to avoid ConstructorInfo.Invoke(). Which takes an object[]. – Hans Passant Feb 20 '12 at 19:16
  • OK I tried to follow the previously linked question and then this one, and my eyes are about to bleed. This seems like a classic XY question, and I think you'd be better served by posting your original code and what you need it to do, rather than asking how to do something totally obscure and quite possibly irrelevant to the original problem. – Chris Feb 20 '12 at 20:54

3 Answers3

3

Not without some form of reflection.

You could create a delegate that uses Activator:

Func<Object[], MyClass> makeClass =
   args => (MyClass) Activator.CreateInstance(typeof (MyClass), args);

makeClass(new object[] {"String", 32});

But this is as close as you can get using a delegate. However, if you create a static helper you can make this much nicer to look at:

public static T BuildType<T>(params Object[] args) {
   return (T) Activator.CreateInstance(typeof (T), args);
}

BuildType<MyClass>("String", 32);

Again, without reflection I'm not sure you can really achieve what you want.

Josh
  • 44,706
  • 7
  • 102
  • 124
  • 2
    I'll just add as a comment that I'm not really sure what you gain from this code. Clearly you know something of what type you want at compile time, and therefore have no need to delegate the construction of that type. Moreover, even if this is something being done at runtime, it represents a level of indirection that simply isn't needed. Just call Activator.CreateInstance() and be done with it. – Josh Feb 20 '12 at 19:22
  • +1: nice helper method and I think it's as close to what OP had in mind (or what you can achieve with C#...) as it gets. – k.m Feb 20 '12 at 19:23
  • And as @Josh mentions above, very close to Activator.CreateInstance. You lose type safety either way, which is kinda painful to see really. I'd delete that code from my project in a heartbeat. – Chris Shain Feb 20 '12 at 19:27
  • Tested, the first is great, except that it dosn't support optional arguments. Object[]{0.2f, abc: 42}, the 2nd is not so good due to the contents of the <> on the BuildType line. I edited the 2nd to not require the template, but there is still the optional parameters thing. – alan2here Feb 20 '12 at 19:36
1

How about something along the folowing lines:

public delegate object VariableParamFactoryFunc(params object[] constructorParams);

public class Factory
{
    private Dictionary<Type, VariableParamFactoryFunc> _registeredTypes = new Dictionary<Type, VariableParamFactoryFunc>();

    public void RegisterType<T>(VariableParamFactoryFunc factoryFunc)
    {
        _registeredTypes.Add(typeof(T), factoryFunc);
    }

    public T Resolve<T>(params object resolutionParams)
    {
        VariableParamFactoryFunc factoryFunc;
        if (_registeredTypes.TryGetValue(typeof(T), out factoryFunc))
        {
            return (T)factoryFunc();
        }
        else
        {
            return default(T);
        }
    }
}

Then you would use it by first registering factory functions: (e.g.)

var factory = new Factory() // or you can turn it into a singleton, this is just for demonstration
factory.Register<IMyInterface>((a, b, c) => new ConcreteImplementation(a, b, c))
factory.Register<CustomClass>((a, b) => new CustomClass(a, b));

and then by actually resolving and consuming those factory functions:

IMyInterface myInterface = factory.Resolve<IMyInterface>(1, "string", 2.412);
CustomClass customClass = factory.Resolve<CustomClass>("param1", 1234);

This should allow you flexibility in Resolving constructors with variable number of parameters without paying the performance penalty of Activator.CreateInstance or Reflection, but you loose in type safety since all parameters will be cast to objects and you will need to cast them back to the right type in the factory function

Good luck!

Anastasiosyal
  • 6,494
  • 6
  • 34
  • 40
  • It's a lot of start understanding and implementing, before I start, does it work with optional parameters referred to by name in the parameter list with ..., name: value, ...? – alan2here Feb 20 '12 at 19:55
  • Also, CustomClass is not like MyType is it? I'm need to not refer to the type name at all on the line where it's used. – alan2here Feb 20 '12 at 19:57
  • This will not work with named optional parameters and it also requires that you specify the type where you want to consume it, so I guess it is not really what you are looking for – Anastasiosyal Feb 20 '12 at 20:02
  • Thanks anyway, I'd send you some rep points for going to the trouble if it were possible. – alan2here Feb 20 '12 at 20:03
0

I'm still not 100% clear on what you are looking for here, but I'll have a swing at it.

I'm assuming that you want to create a delegate that invokes a class's constructor, returning a new instance of that class. The trivial way of doing this is:

var makeX = new Func<String, Integer, X>((s,i)=>new X(s,i));

// To use it
var myX = makeX("Foo", 5);

I am not sure why that isn't 'acceptable' in your use case. If you can point out specific technical or syntactic problems with this approach, I'll try to address them.

EDIT:

Assuming that it is acceptable to predefine your own delegates:

delegate X makeX(String s, Integer i);

then you can do:

var myMakeX = new makeX((s,i)=>new X(s,i));

// To use it
var myX = myMakeX("Foo", 5);

But I suspect that's about as short as you can get. Now, if you know ahead of time that, for instance, all of the time you want to specify "Foo" as the value for s, then you can create another delegate that short-circuits that (using a technique called currying):

var myCurryMaker = new Func<String, Func<Integer, X>>(s => return new Func<Integer, X>(i => new X(s, i)));
var myCurriedX = myCurryMaker("Foo");

// To use it
var myX = myCurriedX(5);
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • It's the "String, Integer" part thats not ideal, if that could be something like typeof(X).ArgTypes instead. – alan2here Feb 20 '12 at 19:17
  • Without the String, Integer part you would lose type safety. I'll add one more possible alternative. – Chris Shain Feb 20 '12 at 19:18
  • So you *want* to lose type safety? – Chris Shain Feb 20 '12 at 20:05
  • Static, Dynamic, Strong and Weak typing are ofc different. I can see you have views on what combinations are safe or not. I'd rather not get into an approaches to type discussion here. See Alan Kay's thoughts on the matter who won the Computing Nobel Prize as well as probably to a lesser extent Eric Lippert's, although I don't want to misquote Eric at all so I won't give examples. I'm a fan of safety in general, even where it creates impracticality to improve provabilty or we have to rethink things. But that dosn't mean things are safer if they are statically typed. – alan2here Feb 20 '12 at 21:00