Browse by Tags

All Tags » .NET (RSS)

Flattening a Jagged Array with LINQ

Today I had to flatten a jagged array .  In my case, it was a string[][] and I needed to make sure every single string contained in that jagged array was set to something (non-null and non-empty).  LINQ made the flattening very easy.  In fact, I ended up making a generic version that I could use to flatten any type of jagged array (assuming it's a T[][]): private static IEnumerable<T> Flatten<T>(IEnumerable<T[]> data) { return from r in data from c in r select c; } Then, checking to make sure the data was valid, was easy: var flattened = Flatten(data); bool isValid = !flattened.Any(s => String.IsNullOrEmpty(s)); You could even use method grouping and reduce the validation to: bool isValid = !flattened.Any(String.IsNullOrEmpty); Technorati Tags: .NET , LINQ , Jagged Array

No Intellisense in Visual Studio 2010 RC1

Well, that's odd.  Why were the default options for Intellisense turned off when I did a fresh install of Visual Studio 2010 RC1? Technorati Tags: .NET , Visual Studio 2010 RC

Lookups vs. Dictionaries

Donn Felker has a great post that explains the different uses of LINQ's ToLookup and ToDictionary .  Check it out! Technorati Tags: .NET , LINQ
Posted by Patrick Steele's .NET Blog
Filed under: ,

Super-simple Object Mapper

If you need a full-featured object mapper with minimal set up, I recommend you take a look at AutoMapper on Codeplex.  If you need a quick-and-dirty solution, maybe the following code could help you out. First off, why am I not using AutoMapper?  At my current client, there are strict rules as to the use of open source software.  There's a process in place for requesting the use of a particular open source tool, but with the red-tape of the approval process (reviews, signatures, justification, etc…), it could literally take 3 – 6 months.  It's just not worth it for what I need right now.  So I rolled my own. This mapper is super-simple, not very smart and may have a bug or two in it, but it works for what I need it to do and reduces a lot of hand coding.  USE AT YOUR OWN RISK! It uses two simple rules to map data between two objects: If a property name and type on the source match the name and type of a destination property, the value is copied. If the user has defined a custom mapping action, use that to copy data (but rule #1 is always executed first). Let's dig into the details. First, I set up a generic class that takes in a couple of types – my source and destination types.  I added a clause on the destination type that it must be 'new-able' so that I could provide a utility function that would create a destination object, map it's values from a source and return it to you. public class Mapper<TSource, TDest> where TDest : new () {   } Copying Properties When copying data from a source object to a destination object, we get all public instance properties of the destination and see if they have a matching (same name and same type) property on the source.  If so, we set the value on our destination object: protected virtual void CopyMatchingProperties(TSource source, TDest dest) { foreach (var destProp in typeof (TDest).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanWrite)) { var sourceProp = typeof (TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.Name == destProp.Name && p.PropertyType == destProp.PropertyType). FirstOrDefault(); if ( sourceProp != null ) { destProp.SetValue(dest, sourceProp.GetValue(source, null ), null ); } } } Custom Transformations I want the ability to define my own transformation for special cases.  This is simply a list of Action<TSource, TDest> delegates: protected readonly IList<Action<TSource, TDest>> mappings = new List<Action<TSource, TDest>>();   public virtual void AddMapping(Action<TSource, TDest> mapping) { mappings.Add(mapping); } Again, simple yet functional. Perform Mappings The last thing we need is a couple of methods to execute the actual mapping: public virtual TDest MapObject(TSource source, TDest dest) { CopyMatchingProperties(source, dest); foreach (var action in mappings) { action(source, dest); }   return dest; }   public virtual TDest CreateMappedObject(TSource source) { TDest dest = new TDest(); return MapObject(source, dest); } You'll see that "CreatedMappedObject" was the reason we needed to have the new-able clause on the TDest generic parameter. Usage Now let's put this into action!  Given a simple domain object and view model: public class DomainObject { public string Name { get; set; } public DateTime DOB { get; set; } public int Age { get; set; } public string Address { get; set; } }   public class ViewModel { public string Name { get; set; } public int Age { get; set; } } As you can see, our view model only needs the Name and Age.  Our mapping code looks like this: var mapper = new Mapper<DomainObject, ViewModel>(); var viewModel = mapper.CreateMappedObject(domainObject); if the view model is created somewhere else and pre-populated with other data, we would use the MapObject method instead of creating a new instance of ViewModel: var mapper = new Mapper<DomainObject, ViewModel>(); var viewModel = InitializeViewModel(); viewModel = mapper.MapObject(domainObject, viewModel); Now let's assume we want to add the user's birth year to the view: public class ViewModel { public string Name { get; set; } public int Age { get; set; } public int BirthYear { get; set; } } Yes, we could pass along the entire date of birth, but this way the view model is getting only what it needs and doesn't need to do any additional processing to get the year: var mapper = new Mapper<DomainObject, ViewModel>(); mapper.AddMapping((source,dest) => dest.BirthYear = source.DOB.Year); var viewModel = mapper.CreateMappedObject(domainObject); To encourage re-use and centralize the setup of any custom transformations, I create a subclass of my Mapper class: public class DomainModelToViewModelMapper : Mapper<DomainObject, ViewModel> { public DomainModelToViewModelMapper() { this .AddMapping((s, d) => d.BirthYear...

Castle Windsor: Turn Off Automatic Property Injection

Suppose you have the following component: public class SomeComponent { public IEmailSender EmailSender { get; set; } } Further, you're using Windsor for IoC and have registered both this component and an IEmailSender in the container.  Whenever 'SomeComponent' is resolved, it's EmailSender will automatically be set by the container.  If you don't want this behavior, you can tell Windsor, on a per-property basis, to not auto-resolve the property with the "DoNotWire" attribute: public class SomeComponent { [DoNotWire] public IEmailSender EmailSender { get; set; } } Source: Turn off property injection/resolution Technorati Tags: .NET , Windsor , IOC , Properties

Using the CodeDom With .NET 3.5 Features

Here it is 2010 and I just noticed while trying to use the CodeDom features of .NET that it defaults to using the .NET 2.0 compiler.  You have to specifically tell it to use .NET 3.5.  Thanks to LukeH and a Stack Overflow question , it was an easy solution: var providerOptions = new Dictionary< string , string > {{ "CompilerVersion" , "v3.5" }}; CodeDomProvider provider = new VBCodeProvider(providerOptions); Yeah, I had to do some integration with some VB.NET code… Technorati Tags: .NET , CodeDom
Posted by Patrick Steele's .NET Blog
Filed under: ,

Live Capture of Log4Net Logging

I recently had to whip up a small diagnostics application for a client.  We were having some connection problems with a component so we wanted to wrap a WinForms GUI around the component and display some debugging information to try and diagnose it. The component was already configured to do some logging using log4net .  The GUI was going to do some additional logging of status information directly in the UI.  The end result was a textbox in the GUI and a log file generated with log4net.  Not bad, but the client asked if we could log everything (specifically, the log4net output) into the GUI – i.e. a single place to review all of the log messages. It took a little bit of searching, but I found an easy way to programmatically add a root appender.  All I needed was an object that implemented IAppender .  Since this was quick-and-dirty and I didn't have a lot of time, my solution was as follows: The main GUI form implemented IAppender: public partial class MainForm : Form, IAppender This interface has a single method – DoAppend .  Implementing this interface doesn't give you all of the bells and whistle's (like PatternLayout or filtering– derive from AppenderSkeleton if you're looking for that), but since this was done in the interest of speed, a quick String.Format was all I needed: public void DoAppend(log4net.Core.LoggingEvent loggingEvent) { ReportProgress(String.Format( "log4net - {0}: {1}" , loggingEvent.Level.Name, loggingEvent.MessageObject.ToString())); } The ReportProgress method simple appends messages to a textbox in the GUI. Finally, in my application's "main", I initialize log4net and then add the main form to the collection of root appenders: XmlConfigurator.Configure();   var mainForm = new MainForm(); ((log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetLoggerRepository()).Root.AddAppender(mainForm); Application.Run(mainForm); Success!  My log4net messages now appear live in the GUI as the components logs them. Technorati Tags: log4Net , IAppender , AppenderSkeleton

Looking Forward to 2010

Yes, the first month of 2010 is almost gone and I'm just now getting around blogging about the past year and the year ahead.  I guess time management should be on my to-do list for this year? The CodeMash Website One of the coolest projects I've worked on in 2009 was the CodeMash website .  Brian Prince and Jim Holmes asked SRT if they wanted to help design a new website for CodeMash.  Brian will be the first to admit that he's an evangelist first, a developer second and a web developer third.  They gave us pretty much free reign to come up with a new idea as well as the freedom to implement the solution however we wanted. We worked with a Inner Circle Media to help plan the new look and feel of the site.  They deserve kudos for the new look.  On the back-end, I used ASP.NET MVC 1.0 along with SQL Server, Linq2SQL and Castle Windsor for my IoC container.  We also integrated with the existing Sharepoint installation for sponsor maintenance, news and session submissions.  User registration was all done in SQL. This was a great learning project.  Registering for a conference is usually a simple process (from the registrants standpoint).  On the back-end, when you're dealing with varying registration costs (based on the current date), discount codes, PayPal, and other things, it can get pretty complicated.  A large suite of unit tests helped us catch a lot of stuff in the beginning, but a few bugs slippped through.  Luckily, nothing major! I want to also thank fellow SRT developers Marina Fedner and Ben Barefield .  Marina helped me out on the user registration portion and Ben was responsible for the REST feed that we all used for our mobile CodeMash applications . Stepping Down from GANG After being involved with the Great Lakes Area .NET Users Group (GANG) for many, many years (webmaster, VP and this last year as President), I did not run for re-election.  There were some other projects I was taking on and last year's vice president David Giard was willing to take the reigns of the group.  Dave did an amazing job last year as VP and is continuing to do great things with GANG in 2010 .  I'll still be around to help out from time to time, but Dave is the man in charge now! VSM's C# Corner After helming Visual Studio Magazine's C# Corner for a number of years, Bill Wagner decided he wanted to devote his time to other things.  He offered my name as a possible successor!  I talked it over with him and VSM Editor in Chief Michael Desmond.  Everything fell into place and I'm now honored to be following in Bill's footsteps as a VSM author.  My first column has been published ( Interface-Based Programming in C# ) and I've got some positive feedback so far.  My next article is in-process and I have to have the first draft done by February 1st or I'll be on someone's naughty list (and it won't be Santa's!). Microsoft C# MVP I was pleasantly surprised on January 1st to receive an email from Microsoft telling me I've received an MVP award for my C# and community work in 2009.  Thanks to Microsoft and other community members I work closely with! 2010 Plans One of the big conferences for 2010, CodeMash , has already come and gone.  It was a great conference and you CAN NOT beat the price.  The amount of content and learning available is unheard of for the price you pay.  I'm already looking forward to CodeMash 2.0.1.1. In February, I'll be attending the MVP Summit in Redmond.  A great chance to get in touch with new technologies, talk with Microsoft reps and mix it up with other MVP's. Michael Eaton is already planning this year's Ann Arbor Give Camp .  I've offered my assistance again this year and will post more on this even as it gets closer. Speaking: I'd like to do more speaking this year.  While I usually get compliments on my presentations, I'm very hard on myself.  I may be a good speaker, but I want to be a great speaker.  That will come with practice.  I've got some idea's for presentations on topics I'm passionate about (specifically, Inversion of Control and Mocking). I'm really looking forward to 2010! Technorati Tags: SRT , MVP , CodeMash , 2010

Use Dependency Injection To Simplify Application Settings

We've all seen and written code that accesses data from our app.config or web.config file.  We'll throw some simple settings in there: <? xml version ="1.0" encoding ="utf-8" ? > < configuration > < appSettings > < add key ="enableLogging" value ="true" /> < add key ="startDate" value ="12/1/2010" /> < add key ="baseFee" value ="157.50" /> </ appSettings > </ configuration > And then we'll use the ConfigurationManager to pull the data out when we need it: public class Foo { public void DoSomething() { bool enableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings[ "enableLogging" ]); DateTime startDate = Convert.ToDateTime(ConfigurationManager.AppSettings[ "startDate" ]); decimal baseFee = Convert.ToDecimal(ConfigurationManager.AppSettings[ "startingFee" ]); } } Now that Inversion of Control and Dependency Injection are part of my everyday development, I don't do it this way anymore.  It's messy and doesn't allow me to easily plug in different values during testing. These days, I create a simple interface for my application settings: public interface IApplicationSettings { bool EnableLogging { get; } DateTime StartDate { get; } decimal BaseFee { get; } } And create an implementation of this interface that pulls data from app.config: public class AppConfigSettings : IApplicationSettings { public AppConfigSettings() { this .EnableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings[ "enableLogging" ]); this .StartDate = Convert.ToDateTime(ConfigurationManager.AppSettings[ "startDate" ]); this .BaseFee = Convert.ToDecimal(ConfigurationManager.AppSettings[ "startingFee" ]); }   #region IApplicationSettings Members   public bool EnableLogging { get; private set; } public DateTime StartDate { get; private set; } public decimal BaseFee { get; private set; }   #endregion } I register my types with my IoC container .  During production, dependency injection takes over and automatically gives me my AppConfigSettings instance.  For testing, I generate a mock IApplicationSettings.  And using these settings just got a whole lot cleaner: public class Foo { public Foo(IApplicationSettings applicationSettings) { } } Technorati Tags: .NET , IOC , Mocking , Castle Windsor , Rhino.Mocks

CodeMash 2.0.1.0 REST Interfaces

This year's CodeMash website has two URI's that expose CodeMash Information: http://www.codemash.org/rest/sessions – All Session Information http://www.codemash.org/rest/speakers – All Speaker Information The main news feed is already exposed via RSS ( http://www.codemash.org/rss ) but we may add a REST interface for that as well. Session Information Here's an example XML response for a single session (note that the <Abstract> content has been removed for brevity): < Sessions xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd ="http://www.w3.org/2001/XMLSchema" > < Session > < URI > /rest/sessions/The-case-for-Griffon-developing-desktop-applications-for-fun-and-profit </ URI > < Title > The case for Griffon: developing desktop applications for fun and profit </ Title > < Abstract > ... </ Abstract > < Start > 0001-01-01T00:00:00 </ Start > < Difficulty > Beginner </ Difficulty > < SpeakerName > Andres Almiray </ SpeakerName > < SpeakerURI > /rest/speakers/Andres-Almiray </ SpeakerURI > </ Session > </ Sessions > The <URI> node contains a URI that will return a single session.  The <Start> node is not set yet since the sessions have not been scheduled.  Once scheduling is complete, it will be available from this feed. Speaker Information Here's an example XML response for a single speaker (again, the <Biography> content has been removed for brevity): < Speakers xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd ="http://www.w3.org/2001/XMLSchema" > < Speaker > < Name > Chris Adamson </ Name > < Biography > ... </ Biography > < Sessions > < SessionURI > /rest/sessions/Oh-Crap-I-Forgot-or-Never-Learned-C </ SessionURI > < SessionURI > /rest/sessions/How-Do-You-Do-That-on-iPhone </ SessionURI > </ Sessions > < TwitterHandle /> < BlogURL /> </ Speaker > </ Speakers > Most speakers only have one session, but some have multiple.  You'll notice that the TwitterHandle and BlogURL for most of the speakers is empty.  That's because we forgot to ask for that information during the speaker submission process (oops!).  We're in the process of collecting the information and as we get it, we'll update our database and the information will be exposed to you. Supported Formats Right now, we support XML (default) and JSON formats.  If you don't add any extension to the REST URI's (or use ".xml") you'll get XML-formatted data (like above).  Use ".json" to return a JSON result.  We could support HTML – perhaps a simple <table> that dumps the information?  Let me know if you think that would be useful. Technorati Tags: .NET , CodeMash , REST Is There An App For That? Do you plan on using these API's to make some cool CodeMash apps?  Keep us updated on your progress by leaving a comment here, blogging about it or via Twitter using the hashtag #CodeMash .  We're looking forward to seeing what you build!
Posted by Patrick Steele's .NET Blog
Filed under: , ,

Using Windsor to inject dependencies into ASP.NET MVC ActionFilters

I'm using Windsor as my IoC container for an ASP.NET MVC application .  To get dependency injection in my controllers, I'm using a slightly modified WindsorControllerFactory from Andre Loker's post earlier this year .  It works great and allows me to easily test my controllers. I've got some custom ActionFilter s that would benefit from dependency injection.  Unfortunately, ActionFilters are attributes on controllers and methods and their instantiation is controlled by the framework.  There is no extension point to allow custom creation of the ActionFilters.  So I can't do constructor dependency injection.  However, I can do the next best thing – property dependency injection! Custom Method Invoker The ASP.NET MVC framework has an extensibility point when it comes to actually invoking actions on controllers.  The default ControllerActionInvoker does everything we need.  We need to modify it's behavior just a little bit to allow us to inject property dependencies. There's a method on ControllerActionInvoker that is used whenever an action with filters is going to be executed.  It's appropriately named "InvokeActionMethodWithFilters".  This method is passed a collection of ActionFilters that have already been created by the framework (see, this is why we can't use constructor injection).  Thanks to Simone Chiaretta for blogging about his custom Ninject-based ActionInvoker , I was able to convert his to a Windsor-based invoker with relative ease: public class WindsorActionInvoker : ControllerActionInvoker { readonly IWindsorContainer container;   public WindsorActionInvoker(IWindsorContainer container) { this .container = container; }   protected override ActionExecutedContext InvokeActionMethodWithFilters( ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary< string , object > parameters) { foreach (IActionFilter actionFilter in filters) { container.Kernel.InjectProperties(actionFilter); } return base .InvokeActionMethodWithFilters(controllerContext, filters, actionDescriptor, parameters); } } As you can see, it's pretty straightforward.  All we do is loop through all of the ActionFilters and inject any dependant properties.  Those of you familiar with Windsor will realize that the IKernel doesn't have an "InjectProperties" method.  I grabbed that from Jeremy Skinner's post about using AutoFac to inject properties into ActionFilters .  He created an extension method that uses reflection to resolve property dependencies: public static class WindsorExtension { public static void InjectProperties( this IKernel kernel, object target) { var type = target.GetType(); foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (property.CanWrite && kernel.HasComponent(property.PropertyType)) { var value = kernel.Resolve(property.PropertyType); try { property.SetValue(target, value , null ); } catch (Exception ex) { var message = string .Format( "Error setting property {0} on type {1}, See inner exception for more information." , property.Name, type.FullName); throw new ComponentActivatorException(message, ex); } } } } } Now that we have the plumbing, let's hook it up! Changes to WindsorControllerFactory My current implementation of WindsorControllerFactor.GetControllerInstance looks like this: protected override IController GetControllerInstance(Type controllerType) { if ( controllerType == null ) { return base .GetControllerInstance(controllerType); } var controller = container.Resolve(controllerType) as Controller;   return controller; } I just need to add a few lines of code to add my custom WindsorActionInvoker (which is registered inside Windsor!): protected override IController GetControllerInstance(Type controllerType) { if ( controllerType == null ) { return base .GetControllerInstance(controllerType); } var controller = container.Resolve(controllerType) as Controller;   // new code if (controller != null ) { controller.ActionInvoker = container.Resolve<IActionInvoker>(); }   return controller; } Using Property Injection with ActionFilters So now lets look at how this can be used to add common data to every page.  The oft-used example of populating a list of sponsors just happened to be the exact scenario I was facing when I researched this solution.  With the WindsorActionInvoker in place, I can now create an ActionFilter to load my sponsor information into ViewData: [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class LoadSponsorsAttribute : ActionFilterAttribute { public ISponsorRepository SponsorRepository { get; set; }   public override void OnActionExecuting(ActionExecutingContext filterContext) { filterContext.Controller.ViewData[ "sponsors" ] = SponsorRepository...

Extension Properties

A couple of weeks ago, a post I made about Strongly Typed Session Variables in ASP.NET led to some interesting comments and discussion.  One of the comments asked "Why can't we just have extension properties…?".  Now, Eric Lippert has provided the answer.  See his " Why No Extension Properties? " post to read about the decisions that go into adding (or not adding) a language feature. Technorati Tags: .NET , C# , Extension Properties

This isn't an error?

I just saw something odd in a diff before a check-in.  Basically, I had a trailing comma in an object initializer: namespace ConsoleApplication1 { class Program { static void Main( string [] args) { var foo = new Foo { A = "A" , B = "B" , }; } }   class Foo { public string A { get; set; } public string B { get; set; } } } I thought this would be an error until I did some searching and found that it is allowed according to " ECMA-334: 19.7 Array initializers ": [Note: Like Standard C++, C# allows a trailing comma at the end of an array-initializer. This syntax provides flexibility in adding or deleting members from such a list, and simplifies machine generation of such lists. end note] Technorati Tags: .NET , C# , ECMA
Posted by Patrick Steele's .NET Blog
Filed under: , ,

Unit Tests and Debug.Assert()

I recently found some code that had a couple of issues: There was a try/catch block that did a "catch(Exception e)".  And no, it didn't rethrow the exception.  See item #2. Inside the exception handler, it had the following code: catch (Exception e) { Debug.WriteLine(e); Debug.Assert( false ); } I thought long and hard on why anyone would do such a thing and I think I know why. Now, this is purely conjecture, but it does explain things.  This code was found inside a custom PropertyDescriptor – namely, the GetValue and SetValue methods both had this code.  I suspect that the Debug.Assert() was placed inside the catch block of these two methods so that no consumers of this component had to worry about possible data binding errors.  If there ever was a problem with data binding, the Debug.Assert(false) would throw up a dialog with a stack trace and some "Abort/Retry/Ignore" buttons, the user would click "Retry" and re-edit their data.  I don't like it, but since this component is used in a number of places in a data binding scenario, that's my theory. The problem for me is that I needed to fix some bugs in this custom PropertyDescriptor and this code had no unit tests.  So before I could consider modifying anything, I had to get the current behavior under test.  Obviously, I had to remove the Debug.Assert(), but, for legacy code, I still had to have that (ugly!) behavior. Since the Debug.Assert() is being used to report errors, I started by defining an interface that would be used for reporting errors: public interface IPropertyDescriptorValueErrorReporting { void SetValueError(Exception e); void GetValueError(Exception e); } I then moved the existing error handling code into a class I called "DefaultPropertyDescriptorValueErrorReporting": public class DefaultPropertyDescriptorValueErrorReporting : IPropertyDescriptorValueErrorReporting { #region IPropertyDescriptorValueErrorReporting Members   public void SetValueError(Exception e) { Debug.WriteLine(e); Debug.Assert( false ); }   public void GetValueError(Exception e) { Debug.WriteLine(e); Debug.Assert( false ); }   #endregion } I then added an overload on the constructor of this custom property descriptor to accept an IPropertyDescriptorValueErrorReporting public CustomPropertyDescriptor( string name, Type type, int index, IPropertyDescriptorValueErrorReporting errorReporting) : base (name, null ) For legacy code, the old constructor is modified to use the "DefaultPropertyDescriptorValueErrorReporting": public CustomPropertyDescriptor( string name, Type type, int index) : this (name, type, index, new DefaultPropertyDescriptorValueErrorReporting()) Finally, we change the exception handler to call our interface methods: catch (Exception e) { errorReporting.GetValueError(e); } And we do a call to SetValueError in the SetValue exception handler. Now I'm done!  I can proceed to create unit tests and I can pass in a mocked IPropertyDescriptorValueErrorReporting and set expectations that the GetValueError and SetValueError methods are called at the appropriate times.  Plus, I've also created an extension point so other applications can hook in to the error reporting and decide how to handle errors on a case-by-case basis. Technorati Tags: .NET , Unit Testing , Mocking

LINQ and Homework

My daughter asked me to check her homework today.  One of the math problems was: A book has 352 pages.  How many 4's were used to print all of the page numbers. She got 35.  She explained how she arrived at that number and while her logic was good, it sounded too low to me.  I started thinking about it a little and then decided I could just write a few lines of C# code to figure out the answer. As Visual Studio was starting up, I pictured the code in my head: initialize a counter to zero.  Loop through an int from 1 to 352 and see if the ToString() contains a 4.  If so, increase the counter.  Just as I was about to start typing, I thought, "Oh wait – I could do all of this with LINQ!". var answer = Enumerable.Range(1, 352).Count(p => p.ToString().Contains( "4" )); How did we ever manage before LINQ?  ;) Technorati Tags: .NET , LINQ , Homework
Posted by Patrick Steele's .NET Blog
Filed under: , ,
More Posts Next page »