Partager via


The Future of C#, Part Two

Well, I intended to spend the last three weeks blogging about C# design process in anticipation of our announcements at the PDC, and then I got crazy busy at work and never managed to do so!

As I'm sure you know by now, we have announced the existence and feature set of that hitherto hypothetical language C# 4.0. Of course I'll be blogging extensively about that over the next year; today though I want to pick up where I left off last time and talk about the tragedy of Object Happiness Disease.

OHD is a disorder believed to be related to Thread Happiness Disease, the belief that threads are awesome and that you should therefore make as many as you possibly can. OHD is an even more common condition which afflicts developers at all levels of experience. Symptoms of OHD in C# developers include:

  • making utterances like "I don't like extension methods because they're not object-oriented"
  • requesting that multiple class inheritance be added to the language
  • writing interfaces only intended to be implemented by one class
  • designing every class to enable derivation, whether it needs it or not
  • and so on.

In short, the Object Happy believe that object-oriented programming is a good in of itself and that OOP principles are objectively (ha ha) good principles.

I don't share these beliefs.

Now, don't get me wrong. I believe that OOP is frequently useful. In fact, it is frequently the best available tool for a great many real-world jobs. OOP works particularly well when you have:

  • multi-person teams of software enginneers,
  • working on solving practical problems,
  • by logically associating the problem data with the functions performed upon them,
  • in a type hierarchy that sensibly models the "is a" and "can do" relationships between the various parts.

And that is where its goodness comes from: OOP techniques make real people more productive in solving real problems and thereby creating value. The techniques are not good in of themselves, they're good because they're practical.

Perhaps surprisingly, our goal in making C# is not to make an object-oriented language. Our goal is to make a compelling and practical language for general-purpose application development on our platforms, and thereby enable our customers to be successful. Making an object-oriented language called C# is just a means to that end, not an end in of itself.

So, yes, the oft-heard criticism that "extension methods are not object-oriented" is entirely correct, but also rather irrelevant. Extension methods certainly are not object-oriented. They put the code that manipulates the data far away from the code that declares the data, they cannot break encapsulation and talk to the private state of the objects they appear to be methods on, they do not play well with inheritance, and so on. They're procedural programming in a convenient object-oriented dress.

They're also incredibly convenient and make LINQ possible, which is why we added them. The fact that they do not conform to some philosophical ideal of what makes an object-oriented language was not really much of a factor in that decision.

Here's another way to think about it.

I had a discussion with Jim Miller a while back about the meaning of the term "functional language" in the context of Scheme. To paraphrase somewhat, Jim's position was along the lines that a "functional language" is one in which the design of the language makes it easy to program in a functional style and difficult (or impossible!) to program in a non-functional style. For example, Scheme does allow mutation of shared state -- a very non-functional-style feature -- but the convention in Scheme is to call attention to the fact that you are doing so by marking mutating functions with a "!". It is possible to program in an object-oriented style in Scheme, but the language resists you at every turn.

I certainly see the merits of Jim's position, but I take a weaker stance. When I say "functional language", I mean a language in which it is possible without undue difficulty to program in a functional style. I do not require the language to work against you if you try to program in a non-functional style in it. For example, I would call JScript a functional language. It is certainly possible to program in a non-functional style in JScript, but you can also treat JScript as Scheme with a slightly goofy syntax if you really want to.

I think about "object-oriented language" the same way; C# is an object-oriented language not because every feature of the language strictly adheres to OO philosophy. Rather, because as a practical matter it is possible to program in an OO style in C# if that's what you want. Adding features that enable other styles of programming does not make C# less of an OO language.

Next time, I'll talk a bit about the "theme" of C# 4.0 and how that influenced the design process. (And if you absolutely positively cannot wait to hear about some of the new language feature in detail, try my colleague Chris Burrows' blog.)

Comments

  • Anonymous
    October 28, 2008
    Eric, I would really appreciate if one of you C# 4.0 gurus could take a glance at the description of new features on Wikipedia: http://en.wikipedia.org/wiki/C_Sharp_(programming_language)#Future_development - and tell if there are some glaring mistakes there that have to be fixed for the sake of encyclopedic accuracy.

  • Anonymous
    October 28, 2008
    There are no glaring mistakes at first glance; there are a couple of very minor errors here and there. Send me your email address and when I have a moment I'll go over the article in detail and email you my errata.

  • Anonymous
    October 28, 2008
    Thank you very much! It's int19h@gmail.com.

  • Anonymous
    October 28, 2008
    Oh, and regarding language features - apparently, there are some puzzlers regarding dark corners of variant generics coming up already (stuff you've covered here previously, but with no definite conclusion then): http://social.msdn.microsoft.com/Forums/en-US/vs2010ctpvbcs/thread/c1e4476d-8be8-41b3-9dae-55b834313ce6

  • Anonymous
    October 28, 2008
    "writing interfaces only intended to be implemented by one class" Give me a built in way to mock a class (without resorting to typemock) that doesn't involve creating an interface/abstract base class, and I will gladly stop.  Bonus point if you can show me this while implementing a MVP pattern (testing the Presenter).

  • Anonymous
    October 28, 2008
    Eric, I suffer from OHD, at least when it comes to the Interface portion for two reasons: Testability and build time abstraction The first is a result of needing the interface so that i can mock my dependencies for unit testing. Can't really see a way around that. True, there will always exist at least two implementations of any interface, but it's still sort of artificial. I guess i'd put extension methods in a similar camp, since unless i attach the extension on an interface, i once again can't test it in isolation. The latter is a result of having to recompile the entire tree of depdendent assemblies if a low level class changes even if the change is internal to the class. By keeping everything behind interfaces and the interfaces in a separate assembly, the build dependence goes away, which on large projects is a signficant time saving. Any way around these limitations without going object and interface happy?

  • Anonymous
    October 28, 2008
    I think maybe I was not entirely clear on that point. What I intended to get across was that I have seen codebases where every class definition also had an identical interface definition for no good reason. Someone had been taught at some point that interface abstraction was A Very Good Thing, and so every time they defined a class, boom, there was a parallel interface right there too. And of course the code was then littered with casts casting values of interface types back to their corresponding class types, because after all, we know that every IFoo is actually a CFoo! If you're writing interfaces that are designed to only be implemented by one class, and you have a really good reason for that, then that's not Object Happiness, that's working around the limitations of a tool in a fairly sensible manner.

  • Anonymous
    October 28, 2008
    @cwbrandsma Can you not declare that virtual which you want to mock? Anyway, thanks for that post, makes me a lot more relaxed about C#'s future! Down with philosophy! Up with pragmatism! (Only a few centuries ago, philosophers were practicians...)

  • Anonymous
    October 28, 2008
    doesn't "makes it easy to program in a ... style" sometimes imply "and difficult (or impossible!) to program in a non-... style"? i.e. when you want to be able to programm e.g. in oo style, you have the implicit requirement, that the things you're using (frameworks, apis, librarys) are also written in that style. otherwise it gets really difficult to use all that cool stuff that's already built in the framework while sticking to the chosen style. I enjoy the mixture of styles in c#/.net (and love the new declarative style), but imho, contrary to your point, it makes the language less object oriented. ok, it's more the framework that is less oo, but those two things go hand in hand somehow.

  • Anonymous
    October 28, 2008
    "Can you not declare that virtual which you want to mock?" You definitely can work around these things, but I'd imagine a lot of people find it a good bit more convenient & clear when working with interfaces, as you can guarantee that a stub/mock/testdouble/whatever won't have any kind of unwanted logic or dependencies.  At work I've had a few cases where someone else's code is working off a base class and, due to things like static methods lurking within (which potentially each pull in dependencies of their own!) I've found it hard to get the tests working without a lot of aggravation.  The moment they use an interface, things go a lot more smoothly.  By implementing the interface in a base class, you get the best of both worlds.  Same code, just an extra interface to worry about. Like the article says, it's possible to go too far, though.  I tend to make an interface for anything that has a tangible effect on my ability to test.  If I am never going to need multiple versions of a class, and that class has few dependencies and little/no impact on my testing, then I don't bother.  Same deal if there is no real 'common' interface and casting would be required.   Rule of thumb is simply what the article said.  Don't do it just to do it.  Do it because it greases the wheels and makes your job easier.  Also, it helps to extract interfaces once the code is fairly stable, otherwise you just end up constantly updating code in two or three places instead of one or two).  

  • Anonymous
    October 28, 2008
    Eric, Thank you for the clarification in the comments about why you wrote your article.  On first read, it did sound like you were discounting OO principles out of hand.  And you're right, there are some programmers who will take OO principles and apply them dogmatically and unthinkingly everywhere, just like there will always be people who will dogmatically adhere to the tenets of <insert your favorite religious or political organization>, and just like there will be people who will walk away from your article proclaiming that object-oriented programming is bad.  There is still no good substitute for thinking.  

  • Anonymous
    October 28, 2008
    No offense meant, but anyone who says that Scheme makes it difficult to program in an object-oriented style simply doesn't know squat about Scheme.  The language does not "resist you at every turn".  In fact, the language features (closures, macros) make it very easy for an average Scheme programmer to write his/her own OO system if he/she wants to.  In fact, this is the real problem with OOP in Scheme: it's so easy to write an OO system that everyone writes a different one.  Check out http://www.plt-scheme.org for a Scheme implementation that has a full-featured object system.

  • Anonymous
    October 28, 2008
    I agree with Thomas, and I'll go one step further and say that it applies even more so to functional programming. One of the big advantages you get from programming functionally are the guarantees of immutability, referential transparency etc, so if you're interfacing with code written in a non-functional style then being able to write your own code in a functional style isn't going to buy you as much as it would in a purely-functional program.

  • Anonymous
    October 28, 2008
    > # writing interfaces only intended to be implemented by one class > # designing every class to enable derivation, whether it needs it or not All well and good, except when it comes to testability. To get great test coverage, it is necessary to mock out objects. Ultimately, the only testability feature built into C# is friend assemblies. I wish that C# was testable without having to use the above features, but it's not. Misko Hevery (testability advocate at Google) says this well in several posts on his blog: http://misko.hevery.com/2008/07/24/how-to-write-3v1l-untestable-code/ http://misko.hevery.com/2008/07/30/top-10-things-which-make-your-code-hard-to-test/ I guess the biggest bugbear from the DI (IoC) meme is that class-based OO languages need to split the .ctor in two, into an object graph constructor and an initialization constructor.

  • Anonymous
    October 28, 2008
    > I wish that C# was testable without having to use the above features, but it's not. TypeMock to the rescue! ... except that some TDD proponents argue against TypeMock because it "promotes bad design" (i.e., does not force you to make an interface for every class, and forcibly decouple things even where it's pointless for any purpose but unit testing). So, it's not quite so simple.

  • Anonymous
    October 28, 2008
    I'll just say one thing that should get an educated reader critique everything written as this was done 15 years back: Mix-Ins.

  • Anonymous
    October 28, 2008
    requesting that multiple class inheritance be added to the language I used to have this symptom very bad, but thankfully with many years of c# therapy i've been cured. for the most part at least. Some days are harder than others . . .

  • Anonymous
    October 29, 2008
    int19h> TypeMock looks interesting. It's an interception system using the profiler API, so I guess it slows things down, but that doesn't matter in a unit test scenario.

  • Anonymous
    October 29, 2008
    Ah... Multiple inheritance... I would gladly stop requesting that if I had a proper way to make mix-ins.... Right now, having a class that implements two interfaces, reuses default behaviour from both, and redefines some from both, is complicated. An OO language should make that simple, you said this yourself !

  • Anonymous
    October 29, 2008
    Regarding object happiness ... in my opinion, the true power of Linq comes not from functional programing alone, but from the fact that it can convert functional constructs to "Objects" ( Expresion<Func<...>> are objects). How I see it is that, Objects  are more flexible than functions, while functions are closed black boxes in most cases, objects are boxes with windows and doors. Generaly functions are composable, while objects can be both composable and decomposable, this is why I'm somewhat object happy in general, but I still like a functional approach where decomposition/modification is not needed or should be avoided (and very much welcome the functional aspect of C#). Dynamic? can't say I'm I have a opinion about it yet, as I'm not a dynamic fan, maybe I would have liked more some Spec#/C(Omega) features at first sigh, but hey :) there's a place for everything, in a dynamic world (literally) even a statically typed language can make a good use of dynamic features. I'm really curious to see how dynamic will work with generics, extension methods, lambda expressions and especially Linq.

  • Anonymous
    October 29, 2008
    Dont forget that there are more ways to regard objects. Personally I use ruby, so I am much more inclined to the dynamic ways of smalltalk. I even enjoy prototype thinking in objects. For me essentially, OOP is finally about messages mostly (that alter a state), then come the objects. I always felt that C++ overcomplicated the whole aspect behind it to the worse. The only amazing thing is that C++ was such a success compared to C, because we still see so much of C++. This, in a way, makes me sad, because frankly C is a huge mess, especially with the whole ecosystem.

  • Anonymous
    October 29, 2008
    > For me essentially, OOP is finally about messages mostly (that alter a state), then come the objects. I always felt that C++ overcomplicated the whole aspect behind it to the worse. Glad to see the Smalltalk "that's not what I meant when I invented the term 'OOP'!" mentality is still alive. :) Do keep in mind though that the very first OO language was still Simula, which was very much statically typed and static overall (and C++ and the rest of the family are all recognizable descendants of Simula).

  • Anonymous
    October 29, 2008
    @Pop: What do you mean by decomposition in this context?

  • Anonymous
    October 30, 2008
    The comment has been removed

  • Anonymous
    November 03, 2008
    Welcome to the 47th Community Convergence. We had a very successful trip to PDC this year. In this post

  • Anonymous
    November 09, 2008
    Welcome to the 47th community convergece. we had a very successful trip to XBOX this year in this post

  • Anonymous
    November 18, 2008
    Sorry I'm late.  Don't sweat it if no one reads this (OO humor). The number one problem I've had with all Microsoft products to date (with one exception) is this:  too many features! I've been a professional engineer/programmer/etc. for 25+ years, and I've used a fair variety of Microsoft products  over the years, as well as non-Microsoft software development products. Please, please, PLEASE, if anyone at Microsoft reads this, consider changing your mind!  Rather than continuing to generalize C#, give us more, more specialized, tools!  The more you generalize C# the less useful it will be for me to solve problem X. I have used a screwdriver as a chisel.  It worked great!  Once.  But after that it only worked as a chisel.  Now that I'm a little older I use the right hand tool for the job.

  • Anonymous
    November 18, 2008
    The comment has been removed

  • Anonymous
    November 25, 2008
    "The techniques are not good in of themselves, they're good because they're practical." You've actually touched upon a serious philosophical here: "inherent value" is nonsense.   Value is a relationship between a valuer and an object; it is not an inherent property like dimension or mass.  For something to have value, there must be a valuer. Thus a software methodology is in of itself utterly devoid of value.  It might however be valuable to certain people in certain contexts.  The same applies to everything in existence - trees, rocks, computers, buildings, paintings, music, even people unless they (or others) see value in themselves.

  • Anonymous
    December 23, 2008
    Very good resources for the coming version... Sam Ng Dynamic in C# Part One Dynamic in C# Part Two Chris

  • Anonymous
    January 05, 2009
    Perhaps deliberately you to not mention that language features come at a cost to everyone involved with the language, not just language designers: every user, every writer of software to support the language (compilers, syntax checkers, syntax highlighters, anything) will be faced with the possibility of meeting applications in the features, whether they like it or not. If the feature is at least non-intrusive, they can still continue their usual ways as long as they only meet and write code that doesn't actually apply the feature; but with intrusive features, that do not just add new syntax but modify the meaning of existing constructs or even programs, they have to understand the feature in depth and revise their existing code right away.  This is what is wrong with extension methods: they should have used a different syntax!

  • Anonymous
    February 23, 2009
    The comment has been removed