If I understand your question correctly, I think the usual way I have seen
this solved is through interfaces.
Several of my customers who did large projects in C# had guidelines that
specified that a class is always an implementation of x interfaces with
very little to no 'own' methods or properties.
That way they passed interfaces (A bit COM like if you want) to different
places.
This obviated the need for forward declarations, with the added contraint
that if you needed to cast from one thing to another, you had runtime type
checking instead of compile time type checking
I think I see what you're getting at here. I don't much like the part about
casting.
I have a system of widgets of various. Each widgets can, for simplicity's
sake, generate alarms and expose operations. I want to separate the code
dealing with these two concerns. That is to say, the alarm logger should be
able to collect alarms from all widgets without having to reference the code
declaring what you can do with an operation. The button for adjusting the
wazzit count should be able to find and run the right operation, without
caring about alarms. And the panic system is based on both, needing to
watch for critical alarms and invoke the reset operation on the widget that
caused the alarm.
With interfaces, I could make the component implement IAlarmSite and
IWithOperations. The alarm type could have a Producer property of type
IAlarmSite. The wazzit adjustment button could use methods from
IWithOperations. But... the panic system has a problem. It can find out
what IAlarmSite caused the critical alarm, but there are no methods for
running a reset operation because those are part of IWithOperations.
Runtime cast.
In C++ there would be no problem. The alarm type could have only a forward
declaration of the component class and return a pointer to it from
GetProducer(). The component class could expose an OperationSet* where that
also is an incomplete type. The alarm logger includes the component
definition and the alarm definition and can get details on what happened and
where. The wazzit adjuster can include the component definition and the
operationset definition and leave all the alarm stuff as incomplete types.
And the panic system can include all three. The only issue is that if
you're careless with your makefile dependency system, you can end up with
ODR violations.
..NET, with generics, has the potential to resolve even that problem, giving
perfect type safety (C++ templates work just as well except for requiring
compiling everything together).
generic<typename Component>
where Component : IAlarmSite
public ref class Alarm
{
...
property Component^ Producer
{
Component^ get( void ) { ... }
}
};
public ref class ComponentImpl : IAlarmSite, IWithOperations
{
typedef Alarm<ComponentImpl> Alarm;
...
BroadcastAlarm(gcnew Alarm());
...
};
and so forth.
But now those generic arguments spread uncontrollably, like a cancer.
public ref class AlarmCollection
{
List<Alarm^> ...; // uh-oh, Alarm needs a type argument
};
Take 2:
generic <typename Component>
where Component : IAlarmSite
public ref class AlarmCollection
{
typedef Alarm<Component> Alarm;
List<Alarm^> ...; // better
};
Take 3:
generic <typename Component>
where Component : IAlarmSite
public ref class AlarmConcern
{
public:
ref class Alarm
{
...
};
ref class AlarmCollection
{
...
};
};
public ref class Component : AlarmConcern<Component>, IAlarmSite
{
};
This is beautiful. The "forward declarations" are centralized and
automatically shared. The typedef is no longer needed, which is good
because it isn't available in C#. Configuration is also centralized, so
it's easy to substitute mock objects. There is just one fatal problem.
Inheritance is the only way to pull a generic class into the name search
scope, and .NET only allows single inheritance. C++/CLI can get around this
partway, with:
public ref class Component : IAlarmSite, IWithOperations
{
typedef AlarmConcern<Component> AlarmConfiguration;
using AlarmConfiguration::Alarm;
using AlarmConfiguration::AlarmCollection;
typedef OperationsConcern<Component> OperationsConfiguration;
// ....
};
This is not so nice anymore, because it requires a using declaration for
every class needed. And it isn't as useful in any language except C++/CLI.
The C# version of "using" can't be used inside a class, so it can't refer to
a generic parameter. Actually, I'm not sure if the C++/CLI should use
"using" or typedef, because the names aren't coming from a base class.
What I've seen a lot of requests for, and wanted a few times myself, is a
sort of "using members" directive, like using, except it pulls static
members (including nested types) of a class into the name search scope. But
this is where such a system would really shine... when used for
configuration of generics, with arbitrary nesting. Imagine:
public ref class Component : IAlarmSite, IWithOperations
{
using members AlarmConcern<Component>; // syntax 1
using OperationsConcern<Component>::*; // syntax 2
...
};
This totally obviates the perceived need to derive from a static (sealed
abstract) class. The power is because the generic configuration can be
passed though:
generic <typename Component>
where Component : IWithOperations
public ref class RemoteOperationsConcern
{
using OperationsConcern<Component>::*;
...
};
Can you suggest a means to accomplish something similar, with the existing
C++/CLI and C# languages?