C# generics — almost useful
This week I have spent an inordinate amount of time doing more C# generics coding than I have before in my life. And there have been very few things in the recent past that has made me feel that I'd rather be coding proper C++, if I couldn't be using Python or Ruby (which would make the task almost trivial).
The problem -- make a set of interfaces to wrap part of the System.DirectoryServices
and System.DirectoryServices.ActiveDirectory
namespaces, so as to make them mockable.
What seemed an easy job of mocking about half a dozen classes and a few methods on each has turned into a major exercise, because they all return different trivial collection classes (like this one) which don't present any public constructors.
Writing a basic Wrapper<T>
to hold a T
provided at construction time, subclassing it to do delegation for standard interfaces like IDisposable
-- or even ICollection
-- no problem, defining interfaces for the parts of the concrete types and writing specific Wrapper
subclasses, easy.
The problem comes when wrapping a collection of type T
, which contains entities of type U
, which have to be exposed as wrapper sub-type V
(subclassing Wrapper<U>
). Not only do I have to suppress FxCop nagging about 3-way generics, but when I write the this[string index]
or the Current
property of a custom Wrapper<IEnumerator>
, I would like to write
return new V(Wrapped[index]);or in expanded form with the types made explicit
U internal = Wrapped[index]; //delegate to wrapped object return new V(internal);
but I can't, because I'm only allowed to get at a public default constructor for V
-- unlike the duck-typing in a C++ template, which would allow me to write the desired code. Which means I'm forced to violate the desired immutability and put a setter on the Wrapped
property, and allow null internal objects. At least I can put a generic wrapping function V Wrap<U,V>(U input)
in the base class which can be the only thing to use it (by making the setter private) -- and then suppress the FxCop nagging on the generic function about having generic types not implied by context.
And talking of FxCop nagging -- on more than one occasion a concrete wrapper named for the wrapped type Win32[NameOfWrappedType]
, or method in the partial interface, got hauled up on violating naming conventions because the MSFT prototype name did so. *sigh* My code gets more and more impure, all the while as I'm simply writing code and unit tests for that code, which I wouldn't need to have written at all if it weren't for unit testing the code I really want to write.
2 comments :
Perhaps the friction you feel is an indication that you are testing and mocking at too low a level. Seriously, you test the collection's implementation and then you TRUST IT. You test the lower-level objects and then you TRUST THEM. And then you mock the much-higher level transactions and know that if the lower-level bits work (and you've got test code to prove it), only your new code can be to blame.
If it were all my code I wouldn't be starting from here...
It's having to substitute for a live AD instance when running unit tests.
Writing a DomainAccessor class and trusting that -- and also providing a mock for the rest of the code to use, fine.
But then it comes time to write the tests for the DomainAccessor, when I have to provide a shimming layer for code inside it that looks like
// Domain -> Forest --> GlobalCatalog gymnastics omitted
DirectorySearcher searcher = catalog.GetDirectorySearcher())
searcher.PropertiesToLoad.Add(...);
searcher.PropertiesToLoad.Add(...");
SearchResultCollection scResults = searcher.FindAll();
foreach (SearchResult scResult in scResults){
foreach (object value in scResult.Properties[...])
// Finally, get data
that I have to write something to model AD in all its gory details, and all the nested special-purpose collection classes (SearchResultCollection, SearchResultValueCollection.
PropertyCollection, PropertyValueCollection).
Using generics means only having to shim the underlying ICollection interface once -- which is a good thing (and I can re-use that for any other bits of .Net library code I have to do mocks for), hence the "almost useful".
The main complaint is about not being able to specify an arbitrary construction interface as a constraint e.g. T : new(U) -- whereas you can blithely assert this in C++ templates (or Ruby or Python code, where I'd get the additional benefit of only needing to parametrise on the return interface).
Post a Comment