.NET Forum / Languages / Managed C++ / June 2007
howto decouple in .NET?
|
|
Thread rating:  |
Ben Voigt [C++ MVP] - 04 Jun 2007 20:04 GMT This is more of a C# question than a C++ question, but my best chance of explaining it is via comparison to C++.
Ok:
In C++ you can forward declare a type. Then references to that type can be passed around in a typesafe way without introducing a dependency on the type. Only users needing to access the members need the type definition.
C# generics provide a similar capability, where you can treat a type as opaque. But C# and .NET assume that the type argument will take on different values, which isn't what I desire. One effect of this is that while the C++ forward declaration is available to all code and automatically refers to the same type everywhere, to get the same effect in C#, the type arguments would spread like cancer. Also, since C# using directives are at namespace scope, you can't use them to emulate a typedef, passing the type arguments back and forth implicitly. What I'm looking for is a module-wide set of type arguments.
Oh... I just had an idea... Let me see if C# supports generic static classes. That might alleviate the pervasive spread of undefined types. Actually, I might not want to declare the class itself static, because that prevents inheritance. The C++ programmer in me cringes at the thought of using a C# partial class as a more powerful namespace, but it might just work.
Bruno van Dooren - 05 Jun 2007 06:51 GMT > This is more of a C# question than a C++ question, but my best chance of > explaining it is via comparison to C++. [quoted text clipped - 21 lines] > thought of using a C# partial class as a more powerful namespace, but it > might just work. 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
Not sure if this helps you at all, but maybe it is useful.
 Signature Kind regards, Bruno van Dooren MVP - VC++ http://msmvps.com/blogs/vanDooren bruno_nos_pam_van_dooren@hotmail.com
Ben Voigt [C++ MVP] - 05 Jun 2007 20:10 GMT > If I understand your question correctly, I think the usual way I have seen > this solved is through interfaces. [quoted text clipped - 7 lines] > 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?
AlexS - 05 Jun 2007 20:18 GMT Why you can't include ResetOperation method in IAlarmSite?
>> If I understand your question correctly, I think the usual way I have >> seen this solved is through interfaces. [quoted text clipped - 159 lines] > Can you suggest a means to accomplish something similar, with the existing > C++/CLI and C# languages? Ben Voigt [C++ MVP] - 05 Jun 2007 20:42 GMT > Why you can't include ResetOperation method in IAlarmSite? Because most users of alarms won't need it. Because if I include every member needed by any user of alarms in IAlarmSite, I would have a huge monolithic, tightly coupled, untestable, unmaintainable application. The information I posted is a small subset of my total system. There's also data being collected, trends being computed, an object lookup system, conversions to various encodings, persistant configuration, and on and on.
Thanks for the advice, which has the advantages of being straightforward and easy to implement, but it's not very effective on a large scale. The stability vs abstraction metric of such a thing would be totally unworkable.
AlexS - 05 Jun 2007 21:05 GMT If I got you right, you already have 2 interfaces on your widgets.
If inclusion of extra method in original interface is not acceptable, you can create 3rd interface with required method, which will be used only in "informed" clients.
As about "huge monolithic, tightly coupled, untestable, unmaintainable", I am not sure I am in sync here. You have 2 interfaces already, so basically your implementing class is that "huge... etc." entity. Also, I am not sure that multiplying interfaces is making architecture small, non-monolithic, testable etc...
Another questionable point is "most users won't need it". Generally speaking it is impossible to predict what users will or won't need in the long run.
However, maybe I miss the real point here.
HTH Alex
>> Why you can't include ResetOperation method in IAlarmSite? > [quoted text clipped - 9 lines] > stability vs abstraction metric of such a thing would be totally > unworkable. Ben Voigt [C++ MVP] - 05 Jun 2007 21:55 GMT > If I got you right, you already have 2 interfaces on your widgets. > [quoted text clipped - 7 lines] > that multiplying interfaces is making architecture small, non-monolithic, > testable etc... You are assuming that there is a Reset method in some interface to provide the reset operation. There is not. Each widget has a dictionary (lookup table if you will) of the available operations, and each operation conforms to a particular interface (think IDispatch::Invoke, it's not that far off). The implementation of the operation is primarily not in the widget class, there is a lot of machinery for discovering operations, logging calls, remote invocation and so forth that is not part of the widget. It is implemented separately and reused among a hierarchy of many different widget classes derived from a common base. Moreover, none of this machinery needs to be visible to the alarm logger, which doesn't execute any operations. But it still has to be reachable from the alarm object by a caller who is interested in calling an operation in response to an alarm, like the hypothetical panic agent. There are a zillion combinations. Some callers will execute operations depending on real-time data. Trends are updated from real-time data. Operations may be triggered from trends. Alarms can be triggered by real-time data, trends, or a combination. I'm trying to avoid coupling all of the logic to an all-knowing widget class...
In native C++ I can maintain that encapsulation by using forward declarations of classes. The widget can have a property exposing its dictionary of available operations. Callers who aren't interested in operations see an incomplete type. Callers who need to work on operations include the associated header file.
In .NET, the way to say "there's this type defined elsewhere, meaningful to some callers" is via generics. I'm trying to find a way to manage generics in such a way that they don't avalanche.
Think how insane it would be for a program needing to show a window to be dependent on digital certificates. Yet to show a window you need a window handle. That window belongs to a process. That process runs in a logon session. That session has an identity token. The identity token matches a particular user. That user has keys to the encrypting file system. In .NET 1.x, none of those can be an incomplete type.
> Another questionable point is "most users won't need it". Generally > speaking it is impossible to predict what users will or won't need in the > long run. Sorry, I meant "callers (code using it)" rather than "users (people using it)"
Chris Mullins [MVP] - 05 Jun 2007 20:47 GMT > 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. If I read your post correctly, and I may well be on a different planet, then extension methods in C# 3.0 will do almost exactly what you're asking for. http://weblogs.asp.net/scottgu/archive/2007/03/13/new-orcas-language-feature-ext ension-methods.aspx
When you use an extension method to "add" a method to a class, such as "Squared" to the Int32 class, then that method shows up (to the outside world) as part of the Int32 class. No extra casting or importing required.
Following your example, you would have you alarm operations extending (and operating on) the alarm class.
Hrm. The more I think about it though, it's not really what you're asking for.
 Signature Chris Mullins, MCSD.NET, MCPD:Enterprise, Microsoft C# MVP http://www.coversant.com/blogs/cmullins
Bruno van Dooren - 06 Jun 2007 12:41 GMT >> If I understand your question correctly, I think the usual way I have >> seen this solved is through interfaces. [quoted text clipped - 10 lines] > I think I see what you're getting at here. I don't much like the part > about casting. Yes, the interfaces indeed allow you to decouple parts of code. The only downside -as you mention- is casting.
Personally, I have not yet encountered problems with that because the implicit assumption is that the objects in question do support the required interfaces, and that all functionality is implemented as an interface. My experience is only with large in-house systems where the environment is contained. i.e. the design has insured that when the project is code complete, all expected interfaces are there.
 Signature Kind regards, Bruno van Dooren MVP - VC++ http://msmvps.com/blogs/vanDooren bruno_nos_pam_van_dooren@hotmail.com
Jeffrey Tan[MSFT] - 05 Jun 2007 08:23 GMT Hi Ben,
Yes, I agree that posting in C# newsgroup will receive more C# MVP and experts replies/thoughts. I am not sure I understand you completely.
Why don't you try System.Type as a member in a static class?
Static class A { Public TypeArg1 As System.Type }
Thanks.
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications.
Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Ben Voigt [C++ MVP] - 05 Jun 2007 20:21 GMT > Hi Ben, > > Yes, I agree that posting in C# newsgroup will receive more C# MVP and > experts replies/thoughts. I am not sure I understand you completely. I'm pretty sure how you understood me isn't even in the same universe as what I meant.
> Why don't you try System.Type as a member in a static class? > > Static class A > { > Public TypeArg1 As System.Type > } I said "In C++ you can forward declare a type. Then references to that type can be passed around...". What I meant was "references to instances of that type can be passed around". Please see my response to Bruno for elaboration.
Jeffrey Tan[MSFT] - 06 Jun 2007 10:28 GMT Hi Ben,
Sorry for the misunderstanding.
Perhaps you are looking for the mechanisms in C# for a ¡°compilation firewall¡±, that is, a mechanism to decouple software components such that both can compile independently and compilation propagation stops right there. If that is the case, then the .NET interface type could help for getting somehow similar effects, depending on the particular situation and constraints, .NET reflection could be of help also.
In C++, one can inadvertently expose implementation details and increase coupling: If Foo embeds a private member Bar, then every user of class Foo also needs to know the layout of class Bar in order to use Foo. In managed code, the problem doesn¡¯t exist ¨C if a class has a private member, the rest of the world doesn¡¯t need to know of its existence. You can easily verify this by declaring a public class Foo, which has a private reference to an ¡°internal class Bar¡± declared in the same assembly.
However, using types ¡°in name only¡± is not possible in .NET. In C++, it is possible for a class Foo to use Bar in its interface as an opaque handle (if the public surface only refers to Bar* or Bar& and an instance of Bar is NOT embedded in Foo, then the clients of Foo never need the definition of Bar). Furthermore, if Foo never uses Bar directly in its implementation (and only passes pointers or references to it to other components instead), then Foo itself never needs the definition of Bar, either. This is not achievable in C# - for instance, you cannot declare a public class Foo with a public method taking or returning a Bar, if Bar is an internal class ¨C you¡¯ll end up getting compiler errors.
If you are really committed to the lost art of uncoupling code, you might want to look at ¡°the other way¡± of doing it ¨C declare Bar as internal and don¡¯t use it in the public interface, but return and take IDs instead, which you can then map to actual objects (in .NET, they can simply declare their interface to use object and do some internal type checks prior to using the thing). Note that there is a cost to this ¨C you will pay by giving up some simplicity.
I don¡¯t think generics have anything to do with this. In .NET all types must be fully defined at compilation time, generics is a run-time mechanism, too late for a ¡°compilation firewall¡±.
Thanks.
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications.
Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Ben Voigt [C++ MVP] - 06 Jun 2007 14:24 GMT > However, using types ¡°in name only¡± is not possible in .NET. In C++, it > is possible for a class Foo to use Bar in its interface as an opaque [quoted text clipped - 9 lines] > a public method taking or returning a Bar, if Bar is an internal class ¨C > you¡¯ll end up getting compiler errors. [snip]
> I don¡¯t think generics have anything to do with this. In .NET all types > must be fully defined at compilation time, generics is a run-time > mechanism, too late for a ¡°compilation firewall¡±. I'm not so sure about that. Generics allow exactly for a class to use another type in its public interface without having a complete definition of that type provided until runtime. Callers can also not have knowledge of the type, if they are also generic.
Oh, but the constraints need to propagate to all callers... meaning the interfaces need to be available again. But I think this time they can propogate in from one side only... I'll see as I start trying to use this method. The problem lies in avoiding clunky generic syntax all over the place (with the corresponding need to make change everywhere when any new generic type argument is needed.
I'm searching for a concept similar to what vhdl provides with configurations.
> Thanks. > [quoted text clipped - 20 lines] > This posting is provided "AS IS" with no warranties, and confers no > rights. Jeffrey Tan[MSFT] - 08 Jun 2007 08:43 GMT Hi Ben,
So you may compare between these .Net/C# mechanisms and decide which one meet your need, thanks.
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== Get notification to my posts through email? Please refer to http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif ications.
Note: The MSDN Managed Newsgroup support offering is for non-urgent issues where an initial response from the community or a Microsoft Support Engineer within 1 business day is acceptable. Please note that each follow up response may take approximately 2 business days as the support professional working with you may need further investigation to reach the most efficient resolution. The offering is not appropriate for situations that require urgent, real-time or phone-based interactions or complex project analysis and dump analysis issues. Issues of this nature are best handled working with a dedicated Microsoft Support Engineer by contacting Microsoft Customer Support Services (CSS) at http://msdn.microsoft.com/subscriptions/support/default.aspx. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|