I have classes that have a constructor such as
constructor Create(Factory: IFactory<IConnection>)
When I try and register the IFactory in the container
Container.RegisterType<IFactory<IConnection>,TConnectionFactory>
or
Container.RegisterType<TConnectionFactory>.Implements<IFactory<IConnection>>
I get an error stating that the interface does not have a guid.
I don't really want to add lots of pointless interfaces such as
IConnectionFactory = interface(IFactory<IConnection>)
['{45106BA8-43E7-4D26-B0EF-1639871B93E4}']
end;
to get around this but is this the only way?
Many thanks
As you found out the generic interface can have a GUID.
That itself causes no harm until you QueryInterface/Supports IList<string> from something that is an IList<Integer> which would erroneously succeed and subsequently fail once you start working with it. FWIW since you mentioned those types while the collection interfaces all have guids there is no need to ever do such querying when using them but they are required internally which then stays within the same generic type argument.
Currently the GUID is being used to check and get the interface from the implementing object because the RTL only supports doing that with a GUID and not with typeinfo.
In fact following code would not raise an exception during the registration but eventually cause defects when being resolved:
RegisterType<IFactory<IConnection>, TSomeFactory> where TSomeFactory implements IFactory<ISomethingElse>
However there is some rather hidden typeinfo available (see the commented line in System.TInterfaceTable) that has the exact typeinfo of the implemented interfaces. Spring4D internally uses that at some places, for example Spring.Reflection.TRttiTypeHelper.GetInterfaces.
That could be used but then there is another catch: generic types across multiple modules have different typeinfo. So it's not so easy to simply use that information to validate during registration and query the interface from the implementing class because right now the container (via some extension) supports registrations and dependencies across multiple modules.
Making the registration more robust and if possible remove the requirement for having a GUID on the interface is something I have on my list for the container refactoring which is planned for later this year.
Related
i added map(), reduce() and where(qlint : string) to a Spring4D fork of mine.
While i was programming these functions, i found out that there is a differnce in the behaviour of the lists, when they are created in different ways.
If i create them with TList<TSomeClass>.create the objects in the enumerables are of the type TSomeClass.
If i create them with TCollections.CreateList<TSomeClass> the objects in the enumerables are of the type TObject.
So the question is:
Is there a downside by using TList<TSomeClass>.create ?
Or in other words: Why should i use TCollections.CreateList<TSomeClass> ?
btw: with TCollections.CreateList i got a TObjectList and not a TList. So it should be called TCollections.CreateObjectList... but that's another story.
Depending on the compiler version many of the Spring.Collections.TCollections.Create methods are applying what the compiler is unable to: folding the implementation into only a very slim generic class. Some methods are doing that from XE on, some only since XE7 ( GetTypeKind intrinsic function makes it possible to do the type resolution at compile time - see the parameterless TCollections.CreateList<T> for example).
This greatly reduces the binary size if you are creating many different types of IList<T> (where T are classes or interfaces) because it folds them into TFolded(Object|Interface)List<T>. However via the interface you are accessing the items as what you specified them and also the ElementType property returns the correct type and not only TObject or IInterface. On Berlin it adds less than 1K for every different object list while it would add around 80K if the folding is not applied due to all the internal classes involved for the different operations you can call on an IList<T>.
As for TCollections.CreateList<T> returning an IList<T> that is backed by a TFoldedObjectList<T> when T is a class that is completely as designed. Since the OwnsObject was passed as False it has the exact same behavior as a TList<T>.
The Spring4D collections are interface based so it does not matter what class is behind an interface as long as it behaves accordingly to the contract of the interface.
Make sure that you only carry the lists around as IList<T> and not TList<T> - you can create them both ways (with the benefits I mentioned before when using the TCollections methods). In our own application some places are still using the constructor of the classes while many other places are using the static methods from Spring.Collections.TCollections.
BTW:
I saw the activity in your fork and imo there is no need to implement Map/Reduce because that is already there. Since the Spring4D collections are modelled after .NET they are called Select and Aggregate (see Spring.Collections.TEnumerable). They are not available on IEnumerable<T> directly though because interfaces must not have generic parameterized methods.
In the spring4d demos, ServiceLocator.GetService<MyType>('Name') is used to resolve the types. But why not use GlobalContainer.Resolve<MyType>('Name')? I don't see any advantage in this approach...
There is one use case, where I use ServiceLocator:
when coding to make legacy code projects unit-testable...
There is an old project, where in mulitiple places, there are constructor calls of an object, which I write tests for (new and changed methods only, in classes, where injection is not possible, e.g. when a Form is created and destroyed in a button event).
In unit-tests, spring4d is helpful to instantiate the class-under-test:
I can use the GlobalContainer in the dpr for the production project and a special (test-only) TContainer-object which is constructed in Testfixture.Setup and destroyed in Testfixture.TearDown... I also re-initialize the global Service-Locator to use my Test-Container (Reason: I have bad experiences to use GlobalContainer in Test, you cannot un-register a type from GlobalContainer in Testfixture.TearDown).
So now, I got a big method in the dpr, where I register all types to GlobalContainer in the production-code project. In the Setup-Method of my test-fixture-class, I register all types needed for the test to my Testing-Container. And in the Methods, that I changed to make them unit-testable, I construct the classes-under-test with ServiceLocator, where formerly constructor-calls where used.
For me, it is the only way to make such a legacy-code project unit-testable... But my strategic goal is to replace most of this code (part-by-part, including the re-initialized ServiceLocators) one day. It is just not possible to replace it now (too much costs, too much risk...).
Suppose:
1) HelpfulUserAtSO answers my SO question with a snippet copied from his production code:
type
IReqBase = Interface(IInterface)
['{B71BD1C3-CE4C-438A-8090-DA6AACF0B3C4}']
procedure FillWithTemplateData;
end;
2) I think Great answer! and blindly copy this into my production code.
3a) We both distribute our apps and user X wants to install both executables on his computer.
What are the consequences?
3b) I buy up HelpfulUserAtSO's company and want to integrate his code (containing the interface definition) into mine (containing the copy. Assume no scoping conflicts).
What are the consequences?
After all a GUID should be 'globally unique'...
If the same GUID are used not within the same process, this is safe to have the same GUID defined. But if, e.g. you access them via COM, it will definitively be confusing.
If you use diverse interfaces with the same GUID in the same process, e.g. by sharing Delphi code units, you may definitively have issues. By convention, an unique GUID should define an unique signature (i.e. set of methods), so the code may think that a given class instance implements all methods of the interface, and it won't be the case. As a result, the internal execution lookup tables (IMT) won't match. You will get a lot of A/V when calling methods.
Take a look at this very complete article for details about how interfaces work, and what is this internal IMT lookup table. The same GUID would mean the same IMT table, which won't be the case for you, so it will just break at runtime.
Just experienced an error due to using duplicated GUID for a new interface that's copied from another.
The consequence was that, since I use Supports, although I was intended to call InterfaceB.Method, but InterfaceA.Method1 got called wrongly even it has a different method signature...I found that with the IDE debugger.
The compiler really should report duplicated interface GUIDs as an error.
I've written a small COM Server in Delphi 2010 that acts as a plug-in into a retail application. The retail application looks for a "discover" interface which registers any number of additional interfaces calling TAutoObjectFactory.Create for each one. This is working just fine--all the plug-in interfaces function as designed.
But now I'd like to call a public method of one interface from another interface so I don't have to duplicate code. Seems simple enough, just call ComClassManager.ForEachFactory looking for the ClassID of the interface I need to use. Got that working, too!
But now that I found the class, I'm stumped by a seemingly trivial final step: how to use or cast the class (or class reference?) I've located to actually call one of its methods.
In the "FactoryProc" I've sent to ForEachFactory, I assume the ComClass property of TComObjectFactory is what I'm after, but it's of type TClass, a class reference to the actual class object to which it points (at least I hope I'm understanding this correctly). I'm a little fuzzy on class references and my attempts to cast or otherwise de-reference this property has resulted in access violations or compiler errors.
Any suggestions?
You're right in your comment, ComClassManager deals with classes, not instances. What you need is (your application-local implementation of) running object table (or something similar), so plugin instances can interact with each other.
How to actually implement it depends on what you really need, e.g. call methods on all running instances, or only on instances of specific classes.
I just hunted down an problem in my mef application; problem was, that I had an [Import] instead of [ImportMany] in my IEnumerable<IFoo> property. I started to wonder why. MEF sees that the injection target is a "collection" and could determine that collection is needed instead of a single element. At least Ninject works this way.
Does anyone have insight why [ImportMany] is required? Only reason I can think of is that one might want to [Export(typeof(IEnumerable<IBar>)] public IEnumerable<Bar> { get; } but is this really the reason for this design? I bet I'm not the only one who has been debugging this kind of error.
It's not the same ;)
[Import] indicates that you want to import a single thing according to a contract. In MEF, a contract is just a string, and when you import a type (like IEnumerable<IBar>), you're really importing according to a contract which is just the name of that type.
In MEF, cardinality is very important, so when you state that you wish to import a single instance of something that fits the stated contract, there can only be a single source. If multiple exports are found, an exception is thrown because of cardinality mismatch.
The [Import] functionality doesn't contain special logic to handle IEnumerable<T>, so from its perspective, it's just a contract like everything else.
The [ImportMany] attribute, however, exists especially to bridge that gap. It accepts zero to any number of exports for the stated contract. This means that instead of having a single export of IEnumerable<IBar> you can have many exports of IBar scattered across multiple assemblies, and there's never going to be a cardinality mismatch.
In the end it's a design philosphy. MEF could have had special, built-in knowledge about IEnumerable<T>. Autofac (and apparently Ninject) does that and call it a Relationship Type.
However, special-casing like that implies that somewhere the implementing code violates the Liskov Substitution Principle, which again can lead to POLA violations, so in this case I tend towards taking side with the MEF designers. Going for a more explicit API may decrease discoverability, but may be a bit safer.
To simplify the above answer slightly:
[Import] will throw an exception if there is more than one matching export.
[ImportMany] will load more than one matching export without throwing an error.
If I have an IDataAccessLayer that I want to import, there should only ever be ONE export available - I'm never going to be writing to 2 databases simultaneously so i use [Import] to ensure that only one will exist.
If I want to load up many different BusinessObjects, I will use [ImportMany] because I want lots of different types of BusinessObjects.