The Weekly Source Code 19 - LINQ and More What, Less How
Dear Reader, I present to you nineteenth in a infinite number of posts of "The Weekly Source Code."
At Mix, Clint Rutkas and I were messing around writing a plugin model for one of his apps. We were prototyping and I typed up this typical-looking plugin style code:
string[] filesToTest = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins"), "*.dll"); foreach (string file in filesToTest) { Assembly a = Assembly.LoadFrom(file); foreach (Type t in a.GetTypes()) { if (!t.IsAbstract && t.BaseType == typeof(MyBaseClass)) { myListOfInstances.Add((MyBaseClass)Activator.CreateInstance(t, credentials)); } } }
It's pretty straightforward and has little (read: no) error handling. It spins through the DLLs in the /plugins folder, loads them each (this should be more explicit rather than a blanket load of all dlls in a folder), then looks for any types that are derived from MyBaseClass that aren't abstract, then instantiates them and puts them in a list.
I'm going to quote Jeff Moser's very good blog post "What does it take to be a grandmaster" in both this post and the next one I write. It's that good. Seriously, go read it now, I'll wait here.
Jeff invokes Anders Heilsberg (Father of C# and LINQ) and:
"Anders' recent statement that future versions of C# will be about getting the programmer to declare "more of the what, and less of the how" with respect to how a result gets computed. A side effect of this is that your "chunks" tend to be more efficient for the runtime, and more importantly, your brain."
I'm going to repeat part I bolded. I like Programming Languages that allow me to declare "more of the what, and less of the how." This how we tried to design the system at my last job and I automatically mentally migrate towards systems that encourage this kind of thinking.
So, back to the foreach loops above. My good friend (and fellow baby sign language fan) Kzu came by while Clint and I were working and suggested we turn this increasingly complex procedure into a LINQ query.
After a few tries, here's what we came up with.
myListOfInstances = (from file in Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins"), "*.dll") let a = Assembly.LoadFrom(file) from t in a.GetTypes() where !t.IsAbstract && t.BaseType == typeof(MyBaseClass) select (MyBaseClass)Activator.CreateInstance(t, credentials)) .ToList();
This is all one line, with some whitespace for readability. It says the same thing as the first chunk of code, but for some minds, it might be easier to read. This isn't a very good example of LINQ shining, from my point of view even though I like reading it. Certainly there's nowhere (I can see) for me to put error handling code that catches a bad load or bad cast in this example. There are, however, a LOT of places within that single line that one could set a breakpoint.
A better example would be like the one Jeff uses:
var primes = new int[] { 2,3,5,7,11,13,17,19,13,29,31,37,41 };
var primeSum = 0;
for (int i = 0; i < primes.Length; i++)
{
primeSum += primes[i];
}
...or even...
var primes = new int[] { 2,3,5,7,11,13,17,19,13,29,31,37,41 }; var primeSum = 0; foreach (int i in primes) { primeSum += i; }
...could be more easily written and read like:
var primes = new int[] { 2,3,5,7,11,13,17,19,13,29,31,37,41 }; var primeSum = primes.Sum();
where .Sum() is part of LINQ as Arrays are IEnumerable.
I totally recommend you go get the free, no-installer LINQPad that comes with 200 examples from C# in a Nutshell.
Here are a few examples that I think really typify pretty code; certainly an improvement over the standard procedural code I'd usually write:
var names = new[] { "Tom", "Dick", "Harry" }.Where(n => n.Length >= 4);
Here's onen that does quite a few things to a list in a fairly clean syntax:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var results = ( from n in names where n.Contains("a") // Filter elements orderby n.Length // Sort elements select n.ToUpper() // Translate each element (project) ).ToList();
What LINQ-style queries have you written that feel more what than how?
Related, ahem, LINQS
- LINQ to Everything - LINQ to XSD adds more LINQiness
- Get namespaces from an XML Document with XPathDocument and LINQ to XML
- Moq: Linq, Lambdas and Predicates applied to Mock Objects
- Hanselminutes Podcast 79 - LINQ to XML
- Improving LINQ Code Smell with Explicit and Implicit Conversion Operators
- XLINQ to XML support in VB9
- 101 LINQ Samples
- LINQ to Amazon
- How Linq to Object Queries Work
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Here is a bit of both the How and then the What:
public static IEnumerable<Control> Flatten(this ControlCollection controls)
{
foreach (Control control in controls)
{
yield return control;
control.Controls.Flatten();
}
}
Now for the What:
LinkButton linkButton = cell.Controls.Flatten().FirstOrDefault(c => c is LinkButton) as LinkButton;
Loading assemblies in current application domain to check that all are abstract or derive from specified class makes a mess. What if non of assemblies are not valid plugins? All will be stay loaded but you can't unload them. But... :) My resolution is described on my blog in polish but there is code for that article. :)
Best regards
var myInstances=from p in Plugins.Find("plugins/") select p;
The 'what' instead of 'how' mindset is becoming more wide spread (good and bad). Good for coding, but potentially bad because even more developers will exist that know less about how stuff works. That aside, I do feel the movement to intent-based (or 'what') development is overdue.
Examples of intent-based movements:
- LINQ (of course)
- Workflow Foundation (on its way)
- WCF (a huge step in the right direction)
- PFX - Parallel Framework Extensions (just awesome)
The intent-based development starts to deteriorate in technologies such as Workflow and WCF when you start trying to host them, because there isn't a declaratively configurable host for the runtimes (especially one that is free). I hope that BizTalk or the Oslo initiative in general will rectify this soon?
-
Add-In:
As for the AddIn space, check out System.AddIn. It is a contract based add-in framework native to the .NET framework that handles the AppDomains. There is also a lot of guidance for implementing and understanding the AddIn framework (down to the effects of using NGEN and working with domain neutrality ). Add-In Team Blog
Code:
http://aspadvice.com/blogs/rbirkby/attachment/34077.ashx
Discussion and pointers to the original code:
http://aspadvice.com/blogs/rbirkby/archive/2007/08/23/Silverlight-Sudoku-with-LINQ.aspx
Also, why does everyone keep insisting that the Sum and Where extension methods for IEnumerable<T> are part of LINQ? They may happen to implement standard query operators that LINQ defines, but IIRC, LINQ stands for Language-INtegrated Queries. What is language-integrated about calling a Sum method? The IEnumerable<T> extensions should not have been defined in the System.Linq namespace for this very reason; they are extraordinarily useful even if you never use the C# or VB LINQ syntax.
It reminds me those 300 char long one liners written in a custom language developed for customization by our business system provider. now we have to upgrade the system, understand and change these kind of statement and it is just the worse nightmare I ever had.
I am sure that there is nicer form to put out these kind of statements that does not drive crazy the poor soul who needs to touch the code couple of years later.
You didn't run into that at all Scott?
I think you nailed it here though:
This is a problem with LINQ in general; in trying to make everything very compact, it leaves too little room to maneuver.
I do find LINQ to be right at the intersection of readable, compact and expressive more often than not, but I wanted to show the good and the bad.
George - Nope, I've never seen a problem with painting in LINQPad.
var names = new[] { "Tom", "Dick", "Harry" }.Where(n => n.Length >= 4);
Is it me, or do you have to read that a couple of times to spot that the => and >= aren't the same operator?
Although I've not come across new[] {...} before - like that a lot. Type inference saves typing - always good.
That flatten method won't work. You're only returning the controls in the top-level control collection, since the return list from "control.Controls.Flatten();" is ignored.
You're not the only one who tends to dump a lot of the new features and framework components from the latest releases of the .NET stack into the "LINQ" pool; many of the bloggers that I read (including many MSFTies) do the same thing. I point it out only because, with the "radical" new features in the latest version of the language (radical to C# anyway), the mental load being put on developers trying to keep up with the language is already severe; there is a tendency to treat things like LINQ as "magic", rather than spend the mental energy trying to understand how these advanced features work. When major players in the community don't differentiate between the various independent components which enable these advanced features, but refer to them all under one feature umbrella, it makes the feature that much more opaque to many who are just trying to keep up. I think that we best serve the community by choosing our words carefully and calling things what they are.
public static IEnumerable<Control> Flatten(this ControlCollection controls)
{
List<Control> list = new List<Control>();
controls.Traverse(c => list.Add(c));
return list as IEnumerable<Control>;
}
public static void Traverse(this ControlCollection controls, Action<Control> action)
{
foreach (Control control in controls)
{
action(control);
control.Controls.Traverse(action);
}
}
This is how I do it in my blog application
Parallel.ForEach(indexContributors,
(contributor) => Parallel.ForEach(contributor.GetIndexableItems(),
(indexableItem) => indexBuilder.AddContent(indexableItem)));
What I am not, so far, particularly fond of, is LINQ itself. My beef with LINQ is that it is superficially similar to pure SQL, but seems hobbled by rigidity and verbosity in odd places. This has repeatedly led me to try to do things that I know I can do quickly and easily in pure SQL, but that are either bulky or impossible in LINQ without breaking it up and throwing in ugly procedural code in the middle. I guess what I'm saying is that the O/R impedance mismatch is still there when you get beyond relatively simple stuff.
So as a SQL "power user", I'd prefer to work with the straight query itself in all but the simplest cases, if I'm interacting with the DB. And for LINQ to objects, I similarly prefer the chained IEnumerable functions when things start to get more complex.
Cheers,
-Dan
Comments are closed.