Hi Luc,
Here was our actual problem (simplified):
1. We had a "Main" project and projects B and C
2. Our "Main" project contained quite some code, including constructing an
instance of a class implemented in B
3. The implementation of project B needed constructing an instance of a
class implemented in C
I thought you had a circular dependency problem, whereby classes in project B reference classes in
project C and classes in project C also reference classes in project B. This circular dependency
doesn't work using project references in Visual Studio (as I'm sure you're aware :). If you define
interfaces in a third assembly, "Project I", then you won't need to have any circular project
dependencies. All classes in project B that reference classes in project C should have interfaces
for project C classes defined in project I, and all classes in project C that have references to
classes in project B should have interfaces for project B classes defined in project I. Class
factories for classes in project B and project C should be located in their own project (A, to use
the name you've given already).
Architecting a solution that doesn't require the use of reflection to construct known classes and
relationships, as described above, seems like it will be a much better approach. The plug-in
architecture is a complex solution (but if you do see value in it then it might be what you need :).
If it's not clear to you why I'm trying to avoid the plug-in approach, it's because you claim this
to be a development issue where you know what all of the interface implementations and their
relationships will be at design-time, and at runtime they will not change. Therefore, there is
absolutely no need for reflection. Also, the plug-in approach requires assembly registration, which
may be a security risk in your solution since it allows third-party developers to plug-in any code
they want that will run with the privileges of your application. To secure against this you must
sign each of your assemblies using the same strong-name and add the
StrongNameIdentityPermissionAttribute to each as well. Then make sure your private strong-name key
is stored in a very safe place. This approach doesn't prevent tampering, however. Anybody can
remove registered assemblies from the configuration causing your application to break without a
trace.
If you need to chain the construction of objects, as in your example, while accounting for circular
dependencies and you'd like to use class factories to do so then here is a solution that might work
for you. Let me know if this is still too primitive for your needs (the names here relate to the
project names used in our examples):
{Project I: Interfaces}
interface IB { }
interface IC { }
{Project B; References: I}
public class B1 : IB
{
public B1(IC c)
{
Console.WriteLine("B1 constructed with: " + c.GetType().ToString());
}
}
public class B2 : IB
{
public B2(IC c)
{
Console.WriteLine("B2 constructed with: " + c.GetType().ToString());
}
}
{Project C; References: I}
public class C1 : IC { }
public class C2 : IC { }
{Project A: Factories; References: I, B, C}
// static classes can be used in C# 2.0 only
public static class BFactory
{
public static IB Create(SomeState state)
{
// Based on SomeState choose the appropriate implementation of IB
// and the appropriate implementation for IC.
// Here, I'm simply checking for null
return (state == null)
? (IB) new B1(CFactory.Create(state))
: new B2(CFactory.Create(state));
}
}
// static classes can be used in C# 2.0 only
public static class CFactory
{
public static IC Create(SomeState state)
{
// Based on SomeState choose the appropriate implementation of IC.
// Here, I'm simply checking for null.
return (state == null) ? (IC) new C1() : new C2();
}
}
{Project Main; References: I, A}
static void Main(string[] args)
{
IB b = A.BFactory.Create(null);
IC c = A.CFactory.Create(null);
Console.WriteLine("BFactory created: " + b.GetType().ToString());
Console.WriteLine("CFactory created: " + c.GetType().ToString());
Console.ReadLine();
}
Don't forget that you'll have to add "using" directives as well, which I removed here for the sake
of simplicity. I created some skeleton projects in VS 2005 with the appropriate references and
added all of the above code. Here was my output:
B1 constructed with: C.C1
BFactory created: B.B1
CFactory created: C.C1
The key here is that the BFactory knew to call into the CFactory, and that each factory knows how to
construct the appropriate type based on some state supplied by the caller (the usual purpose of the
class factory pattern). The factories are hard-coded here, but if you needed more flexibility then
you could use abstract factories instead of static ones. No reflection required, however.
I admit that your needs are still not perfectly clear to me so please don't assume that I know this
to be the best solution for you. This solution might not be enough, or might not work exactly to
meet your requirements, but I feel that it's worth checking out before jumping into reflection,
losing type-safety at compile-time and introducing potential security risks.
--
Dave Sexton
"Luc Kumps" <NO*********@pandora.bewrote in message news:%2***************@TK2MSFTNGP06.phx.gbl...
Dave Sexton wrote:
>Hi Luc,
>>Neither!
This is a development issue only.
We simply want to separate implementation and interface definitions
as good as possible...
That's what I thought at first, but then I started doubting myself
when you wouldn't except my very
simple solution.
Your simple solution was too simple for me to understand the first time
around :-)
Here was our actual problem (simplified):
1. We had a "Main" project and projects B and C
2. Our "Main" project contained quite some code, including constructing an
instance of a class implemented in B
3. The implementation of project B needed constructing an instance of a
class implemented in C
We now understand that your simple solution means that "Main" in C# should
*only*:
1. Create all class factories the other Projects will ever need. In the
above example, a (interface!) reference to the factories for classes C and B
would need to be constructed in the "Main" project. Whenever a project needs
a factory for a class in another project, code needs to be added to the
"Main" project to create an interface reference to this factory.
2. All the non-factory-constructing code of "Main" should be moved to a
separate project A.
3. After instantiating the class factories needed by the other projects,
"Main" should start Project A, containing the actual "Main" code.
Given our C, C++ and Delphi background, this approach was new: we weren't
used to the need to separate [the construction of the class factory] and
[the actual use of the factory] in separate projects (in the above example
this means modifying the "Main" implementation every time we need a class
factory in some other class implementation in another project). But we can
certainly live with that minor quirk, if we get full
interface/implementation separation in return!
Thanks again for your patience!
Luc K