Designing .NET Class Libraries: Designing Inheritance Hierarchies (February 16, 2005)
Posted: Tuesday, February 22, 2005
Please note: Portions of this transcript have been edited for clarity
Introduction
frankred [MS] (Moderator):
Hello & welcome to today’s Designing .NET Class Libraries chat on Designing Inheritance Hierarchies. Now, let’s introduce our knowledgeable experts...
Joe Duffy [MS] (Expert):
Hello everyone! My name's Joe Duffy, and I'm a Program Manager on the CLR team here at Microsoft. I have a blog at https://www.bluebytesoftware.com/blog/ if you're interested. Looking forward to a fun chat. Let the grilling begin! :)
frankred [MS] (Moderator):
Brad Abrams probably won’t be joining us today, as he's still on the road. But, Kit George, also of the CLR Team will be joining us today! Please say hi to Kitg! :-)
Kit George [MS] (Expert):
Hey all! I'm a Program Manager on the Base Classes team, who's helping out trying to answer your questions. I own all the really cool stuff in the BCL such as String, Int... ok, ok, also the REALLY cool stuff like Regex, IO, and Resources
Start of Chat
Kit George [MS] (Expert):
Q: hi kit
A: Hey happy! :D
Kit George [MS] (Expert):
Q: Hi Kitg
A: yo John (A)
Joe Duffy [MS] (Expert):
Q: Kit, is there an RSS feed for your blog?
A: Yes... I'm having problems w/ bandwidth and images on my site, so the RSS link doesn't show up (on the right). It's https://www.bluebytesoftware.com/blog/SyndicationService.asmx/GetRss.
Joe Duffy [MS] (Expert):
Q: In the presentation, Brad mentions that it's almost always a good idea to call a base class constructor. What are examples where this isn't a good idea? Are there any generalities to apply to this?
A: My memory isn't perfect on this one, but I think he stated that you should always explicitly call the base class constructor. This is simply because the C# compiler will automatically inject a call to the no-arguments base class constructor, which isn't always the right thing to do. Being explicit is normally a good idea. Further, if you ever remove that no-arguments base class constructor, your existing code will start failing to compile.
Joe Duffy [MS] (Expert):
Q: When using multiple assemblies, is it good practice to have only one instance of an interface which is used in some/all assemblies? Or is it on a case by case basis?
A: Yes, you should usually factor your assemblies so that you have structured dependencies. Shared interfaces/classes/etc. should live in a shared, single, foundational assembly, not duplicated among many. Of course this is a massive generalization, but a good rule of thumb.
Joe Duffy [MS] (Expert):
Q: When to Use Abstract Vs Interfaces Vs Classes?
A: The decision tree for this is pretty detailed. :) In most cases, using an abstract class is the "right" thing to do if you're trying to create a common class from which others will derive and which isn't fully specified. Interfaces are nice if you don't want to force classes to have a single root class in their hierarchy, as a single class can implement multiple interfaces. Concrete classes should be used if it's fully specified--i.e. subclasses don't have any hooks into which they must provide functionality.
Joe Duffy [MS] (Expert):
Q: General question, when to use Abstraction, Inheritance, Interfaces, aggregation
A: This is difficult, and really needs to be an informed decision based on the design challenge you're facing. (Sounds like a cookie-cutter answer, doesn't it? ;) ) I answered a question just a while ago about using Interfaces vs. Abstract classes--I'd read that for some information here. As far as aggregation over inheritance, basically inheritance is great when you need polymorphic behavior, whereas aggregation is appropriate if you don't require such things... it's also nice because you avoid tying down to a single class hierarchy (since we support only single class-inheritance). I'd recommend reading some pattern books for more details here--such as Design Patterns by Erich Gamma, et al; & Martin Fowler's Analysis Patterns book.
Kit George [MS] (Expert):
Q: Why do you not allow constants to be declared in interfaces? Enums are nifty, but they only support integers.
A: This is one of those historical things from the viewpoint of the capability: we could theoretically support this, the thing we wanted to make sure initially was simply that we allow the key aspects of defining an interface available to users. We continue to get various requests for features on interfaces: probably the most requested one is to be able to support .ctors. Supporting instance data has also been requested, but it's harder to solve, and less clear why you need it. So it's less clear that we would do this in a future release. We'll keep exploring expanding what's possible and cool to support key scenarios.
Joe Duffy [MS] (Expert):
Q: Should interfaces be implemented explicitly unless there is a compelling reason to implement them implicitly? Are there guidelines to follow for when to use each?
A: Explicitly implementing an interface is a good strategy when you want to provide polymorphic compatibility with an existing set of interfaces, but don't want to "clutter" up the public methods of a class. For example, many of the new generics collections use explicit implementation to provide methods for the old-style weakly-typed collections, just so you can plug them into references which expect an old style collection instance. Be careful with explicit implementation, though, when designing for inheritance--explicit implementations are actually private methods, and hence there are limitations with what you can do.
Kit George [MS] (Expert):
Q: Kit: Can you provide some better explanation why .NET Framework Bug #FDBK13103 https://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=bda2f175-19cc-4c90-8938-409693e4889b can't be fixed. I do not really understand the workarounds. Example code should be provided in the docs if you cannot fix it or provide an overload or something that fixes it.
A: Mark, this is a really fair question man, and it ultimately comes down to: we're walking the fine line between app compatibility, and providing you the 'right' solution. Now, we'll be the first team to agree that this is a bug. The problem is that the fix we would have to put in place would change the very way Streams work. Right now, we wait for our internal buffer to be filled before completing, which is the root of the problem. The best fix is to provide a way to say 'return as soon as you have ANY data'. We get into this weird internal state Mark, where we THINK there's more data coming, but in fact, there's not (when the amount of data received is exactly the same as the size of our internal buffer). All that means is that we can't do the fix we want to do because too many users will be relying on the existing behavioral pattern we have (wait before finishing, don't complete as soon as you receive any data). The feature request I allude to in the link you provided would be something like an additional method to instead, return as soon as any data is available. As an aside, whenever we don't fix something like this, compatibility is the reason 99% of the time.
Kit George [MS] (Expert):
Q: Kit: It's too bad only the one who submitted the bug can re-open it, I would have re-opened it many months ago as Mark had let it slip through somehow until recently.
A: Android, its a good point about the submission process: but to abate your concerns that we COULD have done something about this, we couldn't have. The compatibility bar I'm talking about applies to V1.1, so even if we had heard about this 2 years ago, we couldn't have changed the design. In order to provide new methods to solve this we would have had to hear about this probably around 6-8 moths ago. FYI if you want to contact us directly: bclpub@microsoft.com, then you can follow up on an issue directly ;-)
Kit George [MS] (Expert):
Q: Kit, sorry, I am not Mark, but as streams are fundamental and the base for my applications I have watched this bug for a long time. When thinking how the streams work, I thought "how do they know when to return" could be an issue, and it seems it is.
A: Did I answer this with the subsequent response Android?
Kit George [MS] (Expert):
Q: lol Kit... yup, it was... Return key hates me... ;)
A: ;-)
Kit George [MS] (Expert):
Q: Kit, I think I get the core issue .. About the "workaround", is this something that's considered for the documentation? And for network streams, I have not fully understood, is this also an issue if you use sockets and async io with non-blocking flag or select
A: Yeah, it is still an issue even if you mark non-blocking. This IS a bug, it's just one of those horrible bugs we can't fix because we make compatibility commitments (which by the way, I am a huge fan of: they do benefit you at the end of the day). We need to work on the right messaging for the workaround, and it absolutely will be considered for the primary docs.
Kit George [MS] (Expert):
Q: Kit, thanks .. I will try to post a long suggestion on ProductFeedback ;-))
A: cool man
Joe Duffy [MS] (Expert):
Q: What is the guideline in term of how deep the inheritance hierarchy tree can go?
A: There is no hard & fast rule here, but you should limit the depth for both performance and complexity reasons. Having too many classes just adds overhead to the runtime structures (vtables, etc.) which represent instances and static types. An interesting debate happened mid last-year between a few folks on Avalon and Mono, basically because Avalon has fairly deep class hierarchies in many cases. Seehttps://primates.ximian.com/~miguel/archive/2004/Sep-09.html for more info. Hope this helps.
Joe Duffy [MS] (Expert):
Q: Can we use static constructors in interfaces? Why (not)?
A: You can't have a static constructor (also called a class constructor, hence the .cctor token in IL) for an interface. Interfaces are blueprints for types which implement them, and thus don't contain any implementation code. You'd have to use an abstract or concrete class to have a static ctor.
Joe Duffy [MS] (Expert):
Q: Being semi-new to OOP, I'm having a few issues deciding which OO techniques to employ. MSDN does an ok job of explaining what is available under .NET OOP, but not when to use it. Are there any MS links to such info?
A: I'd take a look at our Patterns & Practices information on MSDN https://www.microsoft.com/resources/practices/default.mspx in particular the section on Patterns https://www.microsoft.com/resources/practices/patterns.mspx. I'd also google for "C# Design Patterns" https://www.google.com/search?hl=en&q=C%23+design+patterns, as you should be able to find quite a bit of information "out there." Lastly, there are quite a few books that detail a lot of this, such as Design Patterns by Erich Gamma, et al;. I wouldn't say that OOP in the .NET Framework is much different than general OOP theory that also applies to Java & C++, for example.
Kit George [MS] (Expert):
Q: George, is it possible some time in the future of .net to allow for an enum-like type for strings. I used to do this in vb. but found I can't do this in c#.
A: hmmm... this is interesting. I assume you mean something like this
stringenum VehicleName {
SUV = "SUV",
Sedan = "Sedan",
Hatchback = "Hatchback"
}
It's an interesting idea I haven't seen before. Obviously, languages may make the kind of code I propose here a bit simpler so you just need the entry name, but then you can specifically do enum comparisons on strings. I've gotta say I don't think we'd be likely to support this directly. The reason is that if you're wanting to get the friendly name of the enum, you CAN do this by simply getting the name (and as we see above, mine are just copies). And critically, it's not going to be as fast as using ints or other numeric types to represent the value. On that basis, it's probably not a suggested technique to use.
Joe Duffy [MS] (Expert):
Q: How many of the guidelines for design inheritance will be put into the static code analysis in Team System?
A: You can find most of what will be included in the latest release of FxCop. Check it out here: https://www.microsoft.com/en-us/download/default.aspx?id=6544. You can download the tool and they even have online docs for all of their rules. Of course, the static analysis in Visual Studio Team System is an improved & polished version of this. We don't have many around "designing" for inheritance per se, but we do have rules that cover best practices should you choose to use inheritance. It's tricky to statically determine whether, say, abstract vs. interfaces are the right design choice.
Kit George [MS] (Expert):
Q: Not being able to tie constants to an interface means that Constants need to be declared in a class elsewhere. If you don’t inherit from that class, the constants declared are not part of your interface, which to me is bad.
A: Right. It's a part of defining the contract for the interface that you can't do today. The question really becomes: are the constants you define constants for ALL users of that interface (ie, static), or do you want to simply ensure that individual implementers define their own constants? The former is weird for an interface from a design perspective: the latter is an odd thing to enforce on all your implementers. If you really need this kind of thing, I would suggest a Get method.
Kit George [MS] (Expert):
Q: Constants in interfaces: I'd like to be able to tie the constants that a class uses to that class and still have it be available for re-use across other classes
A: Thanks for the request: I'll add a vote to that feature request ;-)
Kit George [MS] (Expert):
Q: When designing data access classes, I have always read that using a common base class that defines your SQLConnection is always better than using something like:
public class DataAccess { public int GetID() { using(SqlConnection conn = new SqlConnection(){ using(SqlCommand comm = new SqlCommand(){ ... } } } } }
What's the general rule of thumb when designing data classes?
A: We don't actually specify design guidelines for data design. I'm gonna’ point you at https://msdn.microsoft.com/data, which will be your best source to answer this question.
Joe Duffy [MS] (Expert):
Q: When designing a framework, what are some best practices for keeping interfaces generic but still meaningful?
A: This is one of the most difficult things to get right when designing for inheritance. There is a constant tradeoff between simple, universally-applicable interfaces and complex, domain-specific ones. You don't necessarily always have to do one or the other, but you should recognize up front what your goals are (as once you go down one path, it's usually difficult to change direction). There are some techniques, most of which come down to factoring your library correctly. Of course if your interface has a dependency on a particular class in your library, you're pretty limited with regards to staying generic.
Joe Duffy [MS] (Expert):
Q: Why don’t the .NET Framework designers provide unit test-cases for contacts created by System interfaces? Interfaces like IEnumerator or other .. To make sure all implementers of those interfaces - do right thing and will not fail?
A: We do this for CodeDom providers so you can verify compliance with the min provider subset, for example. I love the idea of doing this for some of our more general interfaces, and will definitely pass the suggestion on! This seems to make a lot of sense--we spend a lot of time documenting pre/post-conditions for interface implementations... shipping tests to verify those would be awesome.
Joe Duffy [MS] (Expert):
Q: Could you explain what you mean by "pre/post-conditions for interface implementations"? Do you mean implicit assumptions made by class implementers while implementing an interface?
A: Most interfaces have requirements around what implementers must do in order to "correctly" implement the interface. For example, if you're enumerating over a collection, and the collection changes, the enumerator is no longer consistent and must throw an exception. It'd be great to have a test case that validated your implementation does the right thing in this case. Some are pretty complex (like this example), and though our documentation covers it perfectly, sometimes things get lost in the translation from doc to code. The test is the purest form of validation in this regard. :)
Kit George [MS] (Expert):
Q: Possibly a common question, but why do you only support single inheritence?
A: The scenarios for supporting multiple inheritance is rare enough that we made a fundamental decision when we first created the framework to only support single inheritance. This allows us to avoid SO many pitfalls and problems and issues that frankly, when we were shipping V1, allowed us to get the product out the door way faster than we could have otherwise. Now I know that architects in our organization continue to think about how we might do this in the future, but to be honest, it would be unbelievably hard now, probably harder than it was initially (we'd have that whole compatibility thing to worry about, and we have made fundamental CLR decisions assuming single inheritance is in place). It's one of those decisions that we had to make a LONG time ago, and it affects us from that point on.
Joe Duffy [MS] (Expert):
Q: Is it possible to break a subclass by adding a new virtual or non-virtual method to a base class? E.g. C# requires the use of the override keyword to actually override, but is this just a C# thing or is it enforced by the runtime?
A: Any introduction of a method into a class hierarchy gets marked as "newslot" in IL, and thus this is perfectly legal. In MS-speak we call this a source-breaking change, though, and try to avoid it whenever a customer might have existing subclasses for a given type. This is because C# will emit a warning, asking you to add either "override" or "new" to disambiguate intent. Most customers (including here at MS) treat warnings as errors, and thus code might fail to compile after this change is made. But if you can live with a warning, code will function as if you have marked it with "new" anyhow, even if you do nothing.
Kit George [MS] (Expert):
Q: How do you decide what attributes should be exposed as properties while designing an interface/class?
A: One of the most fundamental questions. Basically, it's a matter of deciding what things a consumer of that interface wants to do with that class in valid scenarios, while also trying to help them not shoot themselves in the head. The defacto starting point we generally take is to give them ALL of the information. And then, we simply ask ourselves the questions surrounding 'what are they now going to do with this?'. If basically, it's going to encourage bad design patterns, our preference is to get rid of it. We have to however, temper that attitude by re-asking 'is there a VALID scenario for that information?'. If the answer is yes, then you would be amazed how quickly a consumer turns up wanting to support that scenario, therefore, we tend to make it available, and then add documentation (guidelines, fxcop rules, etc.) to help people not use it the wrong way. An excellent example is GC.Collect. We strongly encourage people to avoid this baby, and yet there are valid scenarios for its usage.
BradA [MS]
Q: Tools such as FxCop recommend that generic exceptions (System.Exception) not be caught in code. Do you recommended that a windows app let it fall to the unhandled exceptions handler for the entire app or should each entry point/event in a forms app handle it?
A: This is a bit of myth that you should not catch (Exception)… The motivation is good (to prevent you from catching things you can’t handle such as OutOfMember, EEException, etc.) the problem is that in this case the cure is worse than the sickness. In some cases you could end up with 50 or so hard to test catch blocks. I’ll post my CLR Exception and Memory Management sides here https://msdn.microsoft.com/netframework/programming/bcl/ when I am done with the tour and you can read more. That said, if you are going to catch the exception but are not going to do something meaningful, let the exception pass and the user will get a reasonable error dialog provided by the system.
BradA [MS]
Q: Is there any way to track which static constructor is going to initiate first (or the sequence of initialization of constructors) from the inherited classes which are using static constructors?
A: The runtime makes some fairly week guarantees here. In general you need to do very simple work in your cctor that doesn’t have lots of dependences on other cctor’s running. See Chris Brumme’s blog on the topic for all the details. https://blogs.msdn.com/cbrumme/archive/2003/04/15/51348.aspx
Kit George [MS] (Expert):
Q: Why doesn’t the .NET Framework/C# compiler generate alternative operator methods (as CLS requires) automatically? I see a lot of people do not follow CLS and ignore requirements to define alternative names for operators.
A: This is a fair question, and a good suggestion actually, for the VS team. I expect the Visual Studio Team System team to be addressing this kind of issue moving forward by the way. It's typically NOT something we solve at the compiler level though, it's something we generally leave to tools such as fxcop to help you out on. After all, many programmers don't really care about their own compliance, it's just not as important to getting the job done.
frankred [MS] (Moderator):
We're just about out of time. So get your last questions in.
Kit George [MS] (Expert):
Q: Can you offer any advice about forbidding inheritance? E.g. If I have a non-abstract class that overrides Equals, should I prevent subclasses from overriding further otherwise Equals may no longer be symmetric, reflexive, transitive, etc.?
A: The design guidelines specifically state that you should let people subclass and override if you have no other data. Now of course, if you can show there's a security risk (for example) then that would supersede the guidance because you now have an excellent reason to NOT let people override. But otherwise... we encourage both ourselves, and you to keep your classes as open as you can. You've gotta’ trust that people will do the right thing.
Joe Duffy [MS] (Expert):
Q: In C# there are tiny exceptions from the generic rule - variables get initialized in the order they are declared. This can cause side-effects. This is a known issue. My question: How is the order of declarations defined in the case of _partial_ classes?
A: Great question. C# 1.0 does specify that instance fields are initialized in textual order, however I don't see where the 2.0 spec covers this for partial classes. My _assumption_ is that the compiler puts them into the constructor first in the source file order listed at compile time (e.g. partialclass1.cs, partialclass2.cs), and in textual order within each file. E.g. if file pc1 has a + b, and pc2 has c + d, then the result would be a, b, c, d. If you compiled so that pc2 came first, it'd be c, d, a, b. However, I would definitely not rely on this. If ordering matters (especially if your initializations have side effects) you should explicitly declare a constructor which does the initialization in the correct order.
Kit George [MS] (Expert):
Q: Kit, regarding alternative names. What are some good reasons to offset the overhead of declaring alternative method names to developers? Why is this not done automatically? Decorate those methods with an [AutomaticallyGeneratedNoVisibleIfSupportOperators] attribute...
A: I'm not sure what you're suggesting here, can you give me some more explanation?
Kit George [MS] (Expert):
Q: More specific, if I have a base method that has the [XmlElement] attribute specified and I override that method in an inherited class, what would happen?
A: You can mark an attribute as being inherited or not on a base class (see the attribute usage attribute), but you can't prevent it from being inherited on a subclass, IF the base class has said it’s inherited. If the parent says it ain’t inherited, than it goes no further (although a child can add it in then).
frankred [MS] (Moderator):
Alright. We’re out of time. I hope everyone had a good time and got answers to all of their questions. If you have more questions please visit the CLR team blogs https://msdn.microsoft.com/netframework/community/blogs/default.aspx section on the .NET Framework developer center. Please join us again next Wednesday for the next chat in the Designing .NET Class Libraries https://msdn.microsoft.com/netframework/programming/classlibraries/ series.
Joe Duffy [MS] (Expert):
Thanks a lot for coming along. I'm looking forward to another fun chat next Wednesday. Take care... and most importantly, have fun writing code! :)
Microsoft Lover (Guest):
Thanx ((.NET)( joe , Kit & Frank)).ToString();
Top of page