C#
- Jul 22 2009 - performance tuning to an insane level ...
Ok, so I have to admit that I’ve been one to disregard figures around performance when arguing with co-workers over the merit of managed code vs C/C++. I’ve even used the argument that statically typed languages like Java and C# offer more hints to the compiler that allow for optimizations not possible in unmanaged code. I still have a fairly pragmatic view of the spectrum of cost to deliver (skill set/maintainability) vs performance gains… but regardless of all that….. wow this article completely humbled and inspired me.
I don’t know shit.
http://stellar.mit.edu/S/course/6/fa08/6.197/courseMaterial/topics/topic2/lectureNotes/Intro_and_MxM/Intro_and_MxM.pdf - May 8 2009 - Simple Extensibility in .NET ...
I’ve used this approach a few times when I essentially need a really simple plugin / provider model within my applications so I thought I’d jot down the relevant details here for posterity using an old project for adding post commit hooks to subversion.
Consider this a somewhat simplistic approach, not suitable for production code without a bit more plumbing. If you are going all out and need true add-in’s for your .NET based product I recommend checking out the managed add-in framework , very robust stuff and not that hard to implement. In a lot of cases though the isolation, discoverability, communication pipelines etc are a bit overkill. The example I’ll show is a subversion hook that allows for very simple addition of new .NET “actions” to execute on PostCommit. In this case the “add-ins” are only written in house, and editing a config file to hook them up is completely acceptable etc etc.
The solution
Subversion.Contracts : This project is the bridge between our dispatcher and the plugins that will do the work.
Subversion.Plugins : Any of the actions we wish to take post commit are added here, but could just as easily be distributed across as many assemblies and projects as necessary as long as they reference the contracts.
Subversion.Dispatcher : This is the console application that actually receives the arguments from subversion and translates them into our contracts, then executes the appropriate actions (note no references to the plugins project)
The Contract
The contracts are relatively simple, but whatever you put in them this is the interface for the “plugin” that will need to implement. In our case this is IPostCommitHandler :using System;
namespace Subversion.Contracts
{
public interface IPostCommitHandler
{
void ExecuteCommand(PostCommitArgs a);
}
}
Pretty simple, essentially just a “do whatever you want” method that passes the arguments from subversion wrapped up in a simple class. See the attached zip if you want the guts of the subversion specific stuff.
The Pluginusing System;
using Subversion.Contracts;
namespace Subversion.Plugins
{
public class ExecuteForAllCommits : IPostCommitHandler
{
#region IPostCommitHandler Members
public void ExecuteCommand(PostCommitArgs a)
{
SendEmailNotification.SendEmail(a.Argument, a.Revision);
}
#endregion
}
}
Again, very simple and in this case we’re passing off the execution to a static class that again is not shown, but what gets executed isn’t all that important in this case.. simply fill in what you need.
The Dispatcher (Plugin Host)using System;
using System.Collections;
using System.Text.RegularExpressions;
using System.Configuration;
using Subversion.Contracts;
namespace Subversion.Dispatcher
{
///
///
/// Summary description for PostCommit.
///
class PostCommit
{
private static string subversionPath = ConfigurationSettings.AppSettings[“SubversionPath”];
static void Main(string[] args)
{
SubversionRevision rev = ParseRevision(args);
ArrayList commands = DispatchGlobalCommands(rev);
DispatchNamedCommands(rev, commands);
}
private static SubversionRevision ParseRevision(string[] args)
{
SubversionRevision rev;
if (args.Length == 2)
{
rev = new SubversionRevision(subversionPath, args[0], args[1]);
}
else
{
rev = new SubversionRevision(subversionPath, string.Empty, string.Empty);
}
return rev;
}
private static void DispatchNamedCommands(SubversionRevision rev, ArrayList commands)
{
string[] commitLines = rev.CommitLog.Split(Environment.NewLine[0]);
// Handle Named Commands
string registeredCommands = String.Join("|", (string[])commands.ToArray(typeof(string)));
Regex CommandSearch = new Regex(@"(" + registeredCommands + @")\s*:\s*(.+)?", RegexOptions.IgnoreCase);
foreach (string line in commitLines)
{
string lowerline = line.ToLower();
for (Match Matches = CommandSearch.Match(lowerline); Matches.Success; Matches = Matches.NextMatch())
{
string handlerString = ConfigurationSettings.AppSettings[“command:” + Matches.Groups[1].ToString()];
DispatchCommand(handlerString, Matches.Groups[2].ToString(), rev);
}
}
}
private static ArrayList DispatchGlobalCommands(SubversionRevision rev)
{
// Handle global commands
ArrayList commands = new ArrayList();
for (int i = 0; i < ConfigurationSettings.AppSettings.Count; i++)
{
string key = ConfigurationSettings.AppSettings.GetKey(i);
string val = ConfigurationSettings.AppSettings.Get(i);
string[] cmdParts = key.Split(’:’);
if (cmdParts.Length == 2 && cmdParts[0] == “command”)
{
if (cmdParts[1].StartsWith(""))
{
DispatchCommand(val, cmdParts[1].Substring(cmdParts[1].IndexOf(",") + 1), rev);
}
else
{
commands.Add(cmdParts[1]);
}
}
}
return commands;
}
///
/// Call the appropriate method for the command name given with the argument given
/// no processing of the argument happens here.
///
private static void DispatchCommand(string handlerString, string argument, SubversionRevision rev)
{
// We don’t want properly configured commands to stop working because of errors so trap
// everything here…
try
{
if (handlerString != null && handlerString.Length > 0)
{
string[] typeAndAssembly = handlerString.Split(’,’);
if (typeAndAssembly.Length == 2)
{
System.Reflection.Assembly a = System.Reflection.Assembly.Load(typeAndAssembly[1]);
System.Type t = a.GetType(typeAndAssembly[0], true);
object handler = System.Activator.CreateInstance(t);
if (handler is IPostCommitHandler)
{
((IPostCommitHandler)handler).ExecuteCommand(new PostCommitArgs(argument,rev));
}
}
}
}
catch (Exception)
{ //TODO: log errors
}
}
}
}
There is some plumbing in this class that isn’t directly related to this post, but I’ve left it all anyway. Subversion will run this command every time a checkin is made, and the process ends and starts over again each time. This allows for some pretty simple handling of loaded assemblies and whatnot, if you have a longer running process or are dealing with some scale be cautious. ;-)
The Main function has two jobs, parse and create the revision, then read the application configuration file and start issueing commands for the received revision. Commands are in two parts, those defined in config to be executed always (global commands) and those that are interpreted from the subversion commit log itself, parsed out and executed with arguments from the revision log.
Here are some example commands defined in the config<!– Commands –>
<add key=“command:,chris” value=“Subversion.Plugins.ExecuteForAllCommits,Subversion.Plugins” />
<add key=“command:,check-ins” value=“Subversion.Plugins.ExecuteForAllCommits,Subversion.Plugins” />
<add key=“command:bug” value=“Subversion.Plugins.UpdateBugTracker,Subversion.Plugins” />
<add key=“command:cc” value=“Subversion.Plugins.SendEmailNotification,Subversion.Plugins” />- in the key we have “command:[name]” signifies a command arriving in a revision where somewhere in the revision log we’ll see the command name followed by a colon, anything following the colon is then passed to the plugin as an argument. If the name is an asterisk then we simply execute for all, with an optional argument being passed to the plugin. (so the first example emails chris for all revisions, and the second emails an account named check-ins
- the value portion here is what directs the program where to look for the appropriate plugin and class to execute. I copied the format I found in a web.config file which is to put the class name followed by the assembly name separated by a comma.
In retrospect if I were doing something similar again I’d probably create a better structured format rather than relying on all this string parsing… but old code is what it is in this case.
Finally we call DispatchCommand for each parsed out command which is the last piece of this old code that I’m attempting to document here for reuse. DispatchCommand will read the class name and assembly name, load the assembly name and attempt to instantiate the class/type named in order to call it using our IPostCommitHandler interface.
There are a few ways to do this, and for this project I’m simply calling “System.Reflection.Assembly.Load” which relies on the fact that my plugins are located in my bin directory. I’ve also done this using a “plugin store” which is a fancy way to say I had a dynamic path configured that I could read my assemblies from. In this case you can use LoadFile or LoadFrom, LoadFrom will load dependencies automatically while LoadFile loads just the assembly and will potentially load duplicate copies. (see the documentation) In order to get the dll’s in place for this project we just simply add a post build event like so…copy $(TargetDir).* $(SolutionDir)\Subversion.Dispatcher$(OutDir)
If after instantiating the named type from the loaded assembly we actually have an IPostCommitHandler then make the call! Done.System.Reflection.Assembly a = System.Reflection.Assembly.Load(typeAndAssembly[1]);
System.Type t = a.GetType(typeAndAssembly[0], true);
object handler = System.Activator.CreateInstance(t);
if (handler is IPostCommitHandler)
{
((IPostCommitHandler)handler).ExecuteCommand(new PostCommitArgs(argument,rev));
}
So that’s that. You can download the code here - it should basically work as is if you are looking for a shortcut to extending subversion with .NET. I was relatively lazy with getting this posted - so if you got this far, can use the code, and have problems with it leave a comment and I’ll try to help if I can. - Jul 17 2008 - Microsoft's add-in framework and the need for diligence ... We've recently put Microsoft's managed add-in framework (part of .NET 3.5) into very effective use building a plug-in system for a large asp.net application at work. Essentially the framework in place allows other developers (and our own team for out of stream releases) to develop new functionality for our platform that runs the entire life-cycle for a given widget. In our case for this particular widget we're talking about plugins being responsible for up to 4 asp.net controls in different contexts (for example data collection and reporting as two separate controls) as well as a script injection point where plug-ins are able to extend the scriptability of our platform.For us going with the framework gave us a few things we didn't have with our original design for the add-ins.
- Tools to help enforce the pattern
- An extra layer of versioning over the somewhat naive approach we started with
- Built in discovery, provisioning, and a communication pipeline for serializing types and calls across the contracts that make up the interface between host and plugin
- And last but not least support from Microsoft. This is somewhat more minor than the points above, but it helps legitimize our design when we are following the best practices laid out by Microsoft and used by others in similar situations. The documentation and training available also make getting other developers up to speed on the framework that much easier.
Examples :- Referencing an assembly from both the addin and the host that shared code that should have been passed across the pipeline.
- Bypassing the pipeline completely by calling web services from the addin code (client side or server side calling code)
- Conditional code in the host making decisions based on the type of the addin
- Loose coupling based on common knowledge (that shouldn't be common)
In the case of #1 above the shared assembly started off very benign. Essentially some shared utility code for handling urls and some common resource tasks. Why rewrite when that code already existing the main project? Break it off from the project so that it has no dependencies then drop it in. Except that slowly the terrible pain of building contracts, views and adapters for every little interface or interface change drives you towards shortcuts. "Oh I'll just put this code here to test and then fix it later" Even worse are those cases where you've chosen the path of least resistence in dealing with a bug resulting from unexpected behavior with serialization across the pipeline. It only took a few weeks of not being completely on top of this before I discovered our project was littered with types that were being shared directly between host and addin. Any change meant a recompilation of both projects, completely defeating the purpose.
#2 is a legitimate need in our scenario, and we've found ourselves needing to creating proxy services that wrap our own services just to protect against the inevitable change that will follow. Given that third party developers may be writing code for the platform we have to make an effort to protect from change in all of our interfaces, web service or otherwise. In retrospect I think it would have made more sense to strictly enforce a team division so that no one writing addin code was also writing host code.This probably would have gone a long way to preventing these types of problems.
#3 and #4 are a little more insidious and harder to spot without strict code review. #3 for us isn't technically breaking anything in terms of the interface or future versioning, but adds cruft and generally points to a missing method or property on the interface. The last thing you need as the host is to have case statements littered throughout your code looking for addins. #4 took many forms, and in some cases it's fine. An ok example might be sharing enums, which provided they are defined in the contracts or slightly worse something like a utility class is ok. A not ok example for me was code like this : extension.GetSetting("Menu_Text"); which in this case has two errors. One "GetSetting" shouldn't really exist because how an addin chooses to configure itself should be transparent to the host. Second this code depends on the addin having a value defined in it's config file for the key "Menu_Text". This is next to impossible to enforce and can of course easily break.
Replacing this with extension.MenuText; should be trivial, and a no-brainer. When we started using the framework back in December we were rolling the supporting code by hand. To give you a sense of what this entails, this is how you would define an extension who's only job is to return MenuText as in the code above :
IExtensionContract.csusing System.AddIn.Pipeline;
using System.AddIn.Contract;
namespace SimpleExtensionContracts
{
[AddInContract]
public interface ExtensionContract : IContract
{
string MenuText { get; set; }
}
}
IExtension.csnamespace SimpleExtensionContracts.AddInViews
{
[System.AddIn.Pipeline.AddInBaseAttribute()]
public interface IExtension
{
string MenuText
{
get;
set;
}
}
}
IExtension.csnamespace SimpleExtensionContracts.HostViews
{
public interface IExtension
{
string MenuText
{
get;
set;
}
}
}
IExtensionContractToViewHostAdapter.csnamespace SimpleExtensionContracts.HostSideAdapters
{
[System.AddIn.Pipeline.HostAdapterAttribute()]
public class IExtensionContractToViewHostAdapter : SimpleExtensionContracts.HostViews.IExtension
{
private SimpleExtensionContracts.ExtensionContract _contract;
private System.AddIn.Pipeline.ContractHandle _handle;
static IExtensionContractToViewHostAdapter()
{
}
public IExtensionContractToViewHostAdapter(SimpleExtensionContracts.ExtensionContract contract)
{
_contract = contract;
_handle = new System.AddIn.Pipeline.ContractHandle(contract);
}
public string MenuText
{
get
{
return _contract.MenuText;
}
set
{
_contract.MenuText = value;
}
}
internal SimpleExtensionContracts.ExtensionContract GetSourceContract()
{
return _contract;
}
}
}
IExtensionHostAdapter.csnamespace SimpleExtensionContracts.HostSideAdapters
{
public class IExtensionHostAdapter
{
internal static SimpleExtensionContracts.HostViews.IExtension ContractToViewAdapter(SimpleExtensionContracts.ExtensionContract contract)
{
if (((System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(contract) != true)
&& contract.GetType().Equals(typeof(IExtensionViewToContractHostAdapter))))
{
return ((IExtensionViewToContractHostAdapter)(contract)).GetSourceView();
}
else
{
return new IExtensionContractToViewHostAdapter(contract);
}
}
internal static SimpleExtensionContracts.ExtensionContract ViewToContractAdapter(SimpleExtensionContracts.HostViews.IExtension view)
{
if (view.GetType().Equals(typeof(IExtensionContractToViewHostAdapter)))
{
return ((IExtensionContractToViewHostAdapter)(view)).GetSourceContract();
}
else
{
return new IExtensionViewToContractHostAdapter(view);
}
}
}
}
IExtensionViewToContractHostAdapter.csnamespace SimpleExtensionContracts.HostSideAdapters
{
public class IExtensionViewToContractHostAdapter : System.AddIn.Pipeline.ContractBase, SimpleExtensionContracts.ExtensionContract
{
private SimpleExtensionContracts.HostViews.IExtension _view;
public IExtensionViewToContractHostAdapter(SimpleExtensionContracts.HostViews.IExtension view)
{
_view = view;
}
public string MenuText
{
get
{
return _view.MenuText;
}
set
{
_view.MenuText = value;
}
}
internal SimpleExtensionContracts.HostViews.IExtension GetSourceView()
{
return _view;
}
}
}
IExtensionAddInAdapter.csnamespace SimpleExtensionContracts.AddInSideAdapters
{
public class IExtensionAddInAdapter
{
internal static SimpleExtensionContracts.AddInViews.IExtension ContractToViewAdapter(SimpleExtensionContracts.ExtensionContract contract)
{
if (((System.Runtime.Remoting.RemotingServices.IsObjectOutOfAppDomain(contract) != true)
&& contract.GetType().Equals(typeof(IExtensionViewToContractAddInAdapter))))
{
return ((IExtensionViewToContractAddInAdapter)(contract)).GetSourceView();
}
else
{
return new IExtensionContractToViewAddInAdapter(contract);
}
}
internal static SimpleExtensionContracts.ExtensionContract ViewToContractAdapter(SimpleExtensionContracts.AddInViews.IExtension view)
{
if (view.GetType().Equals(typeof(IExtensionContractToViewAddInAdapter)))
{
return ((IExtensionContractToViewAddInAdapter)(view)).GetSourceContract();
}
else
{
return new IExtensionViewToContractAddInAdapter(view);
}
}
}
}
IExtensionContractToViewAddInAdapter.csnamespace SimpleExtensionContracts.AddInSideAdapters
{
public class IExtensionContractToViewAddInAdapter : SimpleExtensionContracts.AddInViews.IExtension
{
private SimpleExtensionContracts.ExtensionContract _contract;
private System.AddIn.Pipeline.ContractHandle _handle;
static IExtensionContractToViewAddInAdapter()
{
}
public IExtensionContractToViewAddInAdapter(SimpleExtensionContracts.ExtensionContract contract)
{
_contract = contract;
_handle = new System.AddIn.Pipeline.ContractHandle(contract);
}
public string MenuText
{
get
{
return _contract.MenuText;
}
set
{
_contract.MenuText = value;
}
}
internal SimpleExtensionContracts.ExtensionContract GetSourceContract()
{
return _contract;
}
}
}
IExtensionViewToContractAddInAdapter.csnamespace SimpleExtensionContracts.AddInSideAdapters
{
[System.AddIn.Pipeline.AddInAdapterAttribute()]
public class IExtensionViewToContractAddInAdapter : System.AddIn.Pipeline.ContractBase, SimpleExtensionContracts.ExtensionContract
{
private SimpleExtensionContracts.AddInViews.IExtension _view;
public IExtensionViewToContractAddInAdapter(SimpleExtensionContracts.AddInViews.IExtension view)
{
_view = view;
}
public string MenuText
{
get
{
return _view.MenuText;
}
set
{
_view.MenuText = value;
}
}
internal SimpleExtensionContracts.AddInViews.IExtension GetSourceView()
{
return _view;
}
}
}Yeah, seriously. One interface and one string accessor requires nine class/interfaces and over 200 lines of code (which obviously could be made less with formatting etc). It's also possible to share the views between addin and host but then you lose part of the more compelling robustness of the framework. If you are interested in where these classes come into play and how the add-in framework actually works check out this link for a good description.Anyway, I can sympathize with the developers in wanting to speed up the process a bit, but the answer is not to bypass the pipeline. The answer is code generation! Thankfully by the time we realized our mistake Microsoft had released a CTP of their pipeline generator which is a nifty little visual studio addin which picks up the output of the Contracts project and uses reflection to find all of the contracts and generate the necessary projects and files for the pipeline. It literally saved us tons of hours and made the addin framework actually usable. Of couse the code generation is only going to work until we version one side or the other, but at that point we should have solidified those interfaces considerably so it will matter a lot less.
Anyway, long story short, the add-in framework is great, but it's really important for the entire team to understand the goal and be diligent in ensuring that all that extra framework code isn't just being wasted by introducing dependencies. - Jun 26 2008 - Enough rope to hang yourself (C# Extension Methods) ...
So I ran into an interesting “gotchya” with C# extension methods tonight. And of course it happens at the 11th hour on a project that is being demoed at 9:00am tomorrow morning. Of course.
Extension Methods
Extension methods are a really cool new feature of C# that were introduced in version 3.0 of the language. Essentially they are static methods that act like instance methods, allowing you to extend objects you don’t own. Ok, seeing those words on screen makes me think this is a terrible idea, but it really does have it’s place. (LINQ relies heavily on it)
When I first saw these I got quite excited because we had a number of scenarios where they would make our code much much cleaner and easier to maintain. I can name a couple examples in our own project where this has the potential to make the API cleaner.Example 1: Helper/Utility/Static Methods
Try as they might, the .NET framework guys will not anticipate every piece of code that ends up repeated hundreds of times across your project to get around a common case. Before the framework added String.IsNullOrEmpty() in 2.0 we had StringHelper.IsNullOrEmpty(string arg)in our code base along with about half a dozen other methods. Now string may not be the best example, because in my mind I think it’s a bad idea to write extensions to framework types, but it does illustrate the problem well.
In our project we have maybe a dozen of these Helper classes full of static methods (utility pattern). They are useful, but the biggest problem is the team’s ability to consistently discover those methods. Emails and other forms of communication helps, code review helps but ultimately you end up with repeated code fragments where helpers could have been used or even worse you end up with competing helpers in different namespaces that need to be consolidated once discovered.Example 2: Enhancing Functionality Based on Context
Another useful place for extension methods in our project is to have our domain model be extended differently based on the area of the application and what rights the users executing that code have. Essentially what we have is two sets of functionality that are implemented very differently based on the context. For example in one context our object model is mapped using an ORM to the database directly, where as in another context that same object model is used with pre-populated data sets that are cached and completely scriptable by our end users. We still have code that needs to understand these objects across this context however, leading us to create interfaces for every single object in that domain that needs to work across these two contexts. I’m a fan of interfaces, but I think in this scenario we’ve clearly lost something in terms of the DRY principle and code readability.
I’ve yet to really map out what this will look like for our project, but I see what’s been done with Linq and am excited about how simple it could be for example to include the “.Scripting” namespace to attach methods to our domain model that are exposed to end users. Similarly an “.API” namespace for our internal privileged code base with everyone sharing the same core objects.The Gotchya that kept me at work an extra hour
Symptoms- Your extension method is no longer ever being executed
- Intellisense shows the correct method signature, reports no problems
- Right click “go to definition” takes you to the method you think will be hit
- Stepping through the code shows you never reach your extension method
I’ve created some fake code to illustrate the problem. Imagine we have a simple factory class providing some useful functions like so :namespace ExtensionMethodsTest
{
public class FactoryA
{
public ObjectA GetInstance()
{
return new ObjectA(“empty object”, -1);
}
public ObjectA GetInstance(string name)
{
return new ObjectA(name, 0);
}
}
}
But then we decide that we actually need to grab instances of ObjectA using an int id, so we add that using an extension method like so :using ExtensionMethodsTest;
namespace MyExtendingNamespace
{
// a container for my extensions
public static class ExtendFactoryA
{
// Extend our factory to look objects up by id
public static ObjectA GetInstance(this FactoryA factory, int id)
{
return new ObjectA(“got by id”, id);
}
}
}
Blamo! Now anytime we’re using the “MyExtendingNamespace” our FactoryA includes the third way to grab an instance of ObjectA.
Here is how I am calling both of these :private void ByIdButton_Click(object sender, EventArgs e)
{
FactoryA fa = new FactoryA();
ObjectA a = fa.GetInstance(42); // call extension method with int
this.label1.Text = a.ToString();
}
private void ByNameButton_Click(object sender, EventArgs e)
{
FactoryA fa = new FactoryA();
ObjectA a = fa.GetInstance(“gotten by name”); // call instance method with string
this.label1.Text = a.ToString();
}
This seems ok, our calls to GetInstance are consistent and we have everything working. Now transplant yourself a couple months into the future when the original owner of the FactoryA class has a need to alter the behavior to allow for more scenarios for retrieving instances of ObjectA.public ObjectA GetInstance(object proxyObj)
{
return new ObjectA(proxyObj.GetType().Name, 0);
}
Now from the calling code everything still appears to work fine, intellisense continues to show the “int” signature that you think you are calling, and in fact if you right click the GetInstance call and say “Go to Definition” it takes you to the MyExtendingNamespace version of GetInstance.
At runtime however the CLR looks first for something that matches your call to fa.GetInstance(42); within the main namespace/class before looking at any extensions. Because our int gets automatically boxed we actually match “object proxyObject” and will never reach the extension method. Depending on the specific implementation in your code this can be a particularly insidious error or it may fail outright. Either way on a large project it can be a real annoyance to track down.
To me there are a few mistakes here. 1) Why use an extension method here at all? 2) In general I think extension methods as overrides make little sense given how the CLR matches these and 3) GetInstance(object proxyObject) should probably be something more specific like ProxyBase or something else that is not an object. (Akin to catching System.Exception, bad… be specific)
Fixing this issue is as simple as renaming the extension method to GetInstanceById, or moving the method onto the Factory itself, or fixing the factory method to not use object. Personally I say drop the extension method.
My example is over simplified, and in our scenario at work there was a little more justification for not changing the class that defined the original functionality. To me though this seems like the tip of an iceberg of problems.- Why negotiate with a module maintainer when I can just add functionality right here right now from my own code?
- Why stop to understand that class and how it’s been constructed when I can just impose my view of how it should work overtop?
- Intellisense makes it easy to find the method, so I don’t have to worry about how I organize this code.
Terrifying. - Jun 21 2008 - here here - use var sparingly ...
When I first read Jeff Atwood’s latest post on his love of C#’s new “var” keyword I was deeply bothered that my co-workers would find the article and latch on to the argument as a justification for laziness. While I do understand his point of view I was bothered by the idea of var statements littered throughout the code base making things more difficult to read for the next developer.
Saving key strokes is never justification for obscuring the code base. If you want to save keystrokes improve your environment, don’t sacrifice your code.
I came across this post on reddit tonight that very nicely counter’s the post on coding horror. Thanks Richard for a voice of reason.
http://richarddingwall.name/2008/06/21/csharps-var- keyword-jeff-atwood-gets-it-all-wrong
