While working on implementing utilities to parse and read an XML doc comment file, I came across the need to abstract the file system for the purpose of testing. This was a perfect time to use the Jolt.Testing library and generate an assembly with a proxy and interface to the static System.IO.File class! My intent was to write the following code.
IFile fileProxy = Mocker.Current.CreateMock<IFile>();
Stream fileContents = new MemoryStream(/* stream data */);
FileReader reader = new FileReader(fileProxy);
This is the example I use in my documentation (creating the IFile interface), and was the prime motivation for creating the Jolt.Testing library. In this test, the file system is never accessed and the IO operation (Open) happens in memory. However, there is a fundamental problem with this code – it won’t compile! Can you see why?
When I saw the compilation error, my heart sank. The Open method returns a FileStream object, not a Stream object (the downcast is not guaranteed to work)! All of a sudden I realized that generating an abstraction to the System.IO.File class accomplished very little since using the Open method still requires that you use the file system. Sure, I could use the OpenText method which returns a StreamReader that is wired to my memory stream (and I ultimately did for this test), but there is still a fundamental problem with the usability of the ProxyTypeBuilder outputs:
A generated interface contains method signatures with concrete types that are intended to be abstracted by the interface.
If you think about it, you would never design a base class to a hierarchy and make references to the concrete types within the base class. Doing so defeats the purpose of creating the base class all together! The same rationale applies to an interface that generalizes some kind of functionality. Recall the wisdom from the GoF: when possible, code against an abstraction, not a concrete implementation.
At this point, I thought my implementation was all for naught. When the shock subsided, I realized that there is flexible solution to the problem, made easy-to-implement by the modularization and design of the ProxyTypeBuilder class. The idea to the fix is as follows. For the ProxyTypeBuilder methods AddMethod and AddProperty (entities that return a value), I will need to add an overload that accepts an override type for the return value. The functions will validate to the make sure that the given type is indeed a base type of the overridden type prior to generating any code. When the generated code is executed and the real subject type returns its object, the proxy will return a reference to the object’s base type, which hides the concrete type, and everything works as expected.
The pros to this solution:
- Option to override is flexible, avoiding the incorrect auto-conversion approach.
- It makes sense to change the return value of FileStream to Stream, but it doesn’t make sense to change DateTime to ValueType.
- Solution extends easily to XML configuration of ProxyAssemblyBuilder.
The cons to this solution:
- Xml documentation for the return value may be incorrect; it may still refer to a concrete type after overriding.
- Generated assemblies for the same type are generally incompatible; you can’t swap one with another as the signature of some methods may be different.
- Developers may have needed a real reference to the concrete type, and so a downcast is required.
- Can’t apply the same idea to method parameters since functions called from the parameters may not exist in the base type.
Apart from the parameter issue that requires investigation, my feeling is that the cons to the solution are negligible. In any case, do let me know what you think. I plan to start work on this feature after the Jolt 0.2 release.