Re:Fear and Loathing in Programming La La Land (2012)
by strager on Monday July 15, 2013 @10:09PM
(#6049664)
What's wrong with:
void Foo<T>(Func<Int, String, T> makeT) where T : IIface {
T myT = makeT(10, "hi");
// ...
}
Foo((x, y) => new MyClass(x, y));
In patternese, this is injecting a factory dependency.
Re:Fear and Loathing in Programming La La Land (2012)
by sparkie on Monday July 15, 2013 @10:15PM
(#6049682)
I completely forgot about that pattern, not used C# in a while. Yes, this works for cases where you know what class you might pass in ahead of time, and can say new MyClass (although might break the principle of least astonishment). There are cases when you can't use that pattern though: for example, when you have some plugin framework, where plugins are loaded dynamically at runtime, you search for classes implementing an IPlugin interface, and create a new instance of said class.
Reflection must be used in that case. Using `new T()` for parameterless ctors is just a call to Activator.CreateInstance<T>(), but there are no generic CreateInstance overloads taking arguments.
C# doesn't allow you to specify in an interface or abstract class the parameters that a constructor must take: which makes it very difficult to use such classes as generic type arguments for instance. There's a new() constraint which allows you to call a parameterless constructor, and if you also constrain the generic type argument to some interface, you can set the data you want through it.
If you expect parameters in the constructor though, you can't simply create a new object of the type passed as a generic argument. You must use reflection (Activator.CreateInstance(typeof(T) ...), assuming that there exists a constructor for the yet unknown type (obviously not statically checkable). To prove such a constructor exists at runtime, you can use typeof(T).GetConstructors(), and search through the returned collection for a ConstructorInfo with matching arguments. If you get this far, you'll probably drop the idea and resort to the create-set-call mess instead.
I suppose a way of fixing this would be to introduce some kind of "constructor pattern" into interfaces and non-sealed classes, requiring subclasses to implement it and allowing the use of said constructor pattern as a generic type argument constraint.
Another time you might need create-set-call is if constructing objects which have a mutual reference to eachother. [ var a = new A(); var b = new B(a); a.setB(b); ] cannot be expressed with parameters in both ctors. This might seem like a "bad idea" due to the circular reference, but it's more common than you'd think, and doesn't necessarily mean bad code.