.Net Code Monkey RSS 2.0
 Wednesday, February 10, 2016

Centralised Event Dispatcher in C# - Part 1

In this series of posts I am going to share with you the solution I have used in a recent project for a centralised event dispatcher. All of the source code will be available in my public GitHub repositories. The concept I have tried to realise is a central class that can raise events for any listener to subscribe to.

Assumptions

It is assumed that the reader of this article already has a good understanding of coding with C#, can create projects and solutions, classes and Windows Forms, and can add references to a project and import references into a class. It is also assumed the reader understands basic inheritance and interface implementation.

Solution

I have created a solution with two projects, one a windows forms application project which I have called `Dibware.EventDispatcher.UI` and the other is a class library called `Dibware.EventDispatcher.Core`. I will place all of the event dispatcher code in the class library so that if you like the solution then you can just pick the DLL up and start using it in your own projects without the clutter of the consuming code.

Dibware.EventDispatcher.Core

First I want to define a contract in the class library that all event objects will adhere to. I will name this `IApplicationEvent` and it will be an empty public interface in a folder named `Contracts`.
public interface IApplicationEvent { }
Any method that wants to handle any of the events that the dispatcher will publish will need to conform to a predefined method signature. This will be defined by the `ApplicationEventHandlerDelegate` delegate in the `Contracts` folder.
   
public delegate void ApplicationEventHandlerDelegate<in TEvent>(TEvent @event) where TEvent : IApplicationEvent;
This delegate has a generic type parameter `TEvent` which can be contravariant and will take a single argument of the generic type (which will be the event object) but the type of the event object will be constrained to implement the `IApplicationEvent` interface.

In the same folder I will create a public interface contract which the event dispatcher will adhere to, named `IApplicationEventDispatcher`. This interface will have three members, one to add a listener to the dispatcher, one to remove a listener from the dispatcher, and one to dispatch an event. The listeners must adhere to the signature defined by the `ApplicationEventHandlerDelegate`. The `Dispatch` method will take an argument which adheres to the `IApplicationEvent` interface. The `IApplicationEventDispatcher` will also demand that `IDisposable` is implemented so that any resources can be cleared up properly.
    public interface IApplicationEventDispatcher : IDisposable
    {
        void AddListener<TEvent>(ApplicationEventHandlerDelegate<TEvent> handler) where TEvent : IApplicationEvent;
        void RemoveListener<TEvent>(ApplicationEventHandlerDelegate<TEvent> handler) where TEvent : IApplicationEvent;
        void Dispatch<TEvent>(TEvent @event) where TEvent : IApplicationEvent;
    }
Now we can create the public dispatcher class itself, and we will put this in the root of the class library assembly. We will call it `ApplicationEventDispatcher` and it will implement the `IApplicationEventDispatcher` interface and inherently `IDisposable`.

public class ApplicationEventDispatcher : IApplicationEventDispatcher
 {
     public void Dispose()
     {
         throw new System.NotImplementedException();
     }

     public void AddListener<TEvent>(ApplicationEventHandlerDelegate<TEvent> handler) where TEvent : IApplicationEvent
     {
         throw new System.NotImplementedException();
     }

     public void RemoveListener<TEvent>(ApplicationEventHandlerDelegate<TEvent> handler) where TEvent : IApplicationEvent
     {
         throw new System.NotImplementedException();
     }

     public void Dispatch<TEvent>(TEvent @event) where TEvent : IApplicationEvent
     {
         throw new System.NotImplementedException();
     }
 }
So lets correctly implement the Dispose pattern within the class.
private bool _disposed;

 ~ApplicationEventDispatcher()
 {
     Dispose(false);
 }

 public void Dispose()
 {
     Dispose(true);
     GC.SuppressFinalize(this);
 }

 private void Dispose(bool disposing)
 {
     if (_disposed) return;

     if (disposing)
     {
         // free other managed objects that implement IDisposable only
     }

     // release any unmanaged objects
     // set the object references to null

     _disposed = true;
 }
The next task is to create a backing store for the event handlers. For this we will use a dictionary where the key is the type of the event which is to be handled and the value is the delegate that will handle the event. This dictionary will be initialised in the class constructor. We will also need to dispose of it any any delagates within it properly later. but for now we will just set the reference to null in the `Dispose(bool)` method just before setting `_disposed = true;`.
private Dictionary<Type, Delegate> _applicationEventHandlers;

 public ApplicationEventDispatcher()
 {
     _applicationEventHandlers = new Dictionary<Type, Delegate>();
 }

 private void Dispose(bool disposing)
 {
     if (_disposed) return;

     if (disposing)
     {
         // free other managed objects that implement IDisposable only
     }

     // release any unmanaged objects
     // set the object references to null

     _applicationEventHandlers = null;

     _disposed = true;
 }
We can now focus on adding listeners to the dispatcher by adding implementation into the empty `AddListener` method. The first task is to see if our dictionary store already has any delegates for the type of event handler we are adding. If we do then we get a reference to the delegates and combine the new one with them. If there id not one present then we add the handler into the dictionary using the event type as the key.
public void AddListener<TEvent>(ApplicationEventHandlerDelegate<TEvent> handler)
     where TEvent : IApplicationEvent
 {
     Delegate @delegate;
     if (_applicationEventHandlers.TryGetValue(typeof(TEvent), out @delegate))
     {
         _applicationEventHandlers[typeof(TEvent)] = Delegate.Combine(@delegate, handler);
     }
     else
     {
         _applicationEventHandlers[typeof(TEvent)] = handler;
     }
 }
If what goes up must come down then what gets added must be given a fair crack of the whip to be removed. We can provide this by adding implementation into the empty `RemoveListener` method. Again the first task is to see if we have any evens in the dictionary for the type of the event. If we do then we can look to see if our handler is in the delegates, and if it  remove it from the delegate invocation list. If there are no more delegates in the invocation list then remove the dictionary entry altogether.

public void RemoveListener<TEvent>(ApplicationEventHandlerDelegate<TEvent> handler)
     where TEvent : IApplicationEvent
 {
     Delegate @delegate;
     if (_applicationEventHandlers.TryGetValue(typeof(TEvent), out @delegate))
     {
         Delegate currentDel = Delegate.Remove(@delegate, handler);

         if (currentDel == null)
         {
             _applicationEventHandlers.Remove(typeof(TEvent));
         }
         else
         {
             _applicationEventHandlers[typeof(TEvent)] = currentDel;
         }
     }
 }
Now we have methods to add handlers to and remove them from our dispatcher lets provide some functionality to dispatch an event to any subscribed listeners. If the event passed is null then throw an exception straight away, otherwise use the type of the event to look for handlers of that event in the dictionary, if there are handlers for the event, then invoke them!
public void Dispatch<TEvent>(TEvent @event) where TEvent : IApplicationEvent
 {
     if (@event == null)
     {
         throw new ArgumentNullException("event");
     }

     Delegate @delegate;
     if (_applicationEventHandlers.TryGetValue(typeof(TEvent), out @delegate))
     {
         ApplicationEventHandlerDelegate<TEvent> callback = @delegate as ApplicationEventHandlerDelegate<TEvent>;
         if (callback != null)
         {
             callback(@event);
         }
     }
 }
Theoretically all code that subscribes to the event dispatcher's events will detach their own handlers, but being aware that subscribing code may be not carry out this task we need to ensure all handlers are disconnected when this event dispatcher is disposed. For this we will add a `RemoveAllListeners` method which we will call from the `disposing` code path in `Dispose(bool)`.
  private void Dispose(bool disposing)
 {
     if (_disposed) return;

     if (disposing)
     {
         // free other managed objects that implement IDisposable only
     }

     // release any unmanaged objects
     // set the object references to null
     RemoveAllListeners();

     _applicationEventHandlers = null;

     _disposed = true;
 }
And this method will gather all of the handler types, iterating through them un-wiring all of the delegates in the invocation lists and finally when no delegates exist for the handler type, removes the dictionary entry.
private void RemoveAllListeners()
 {
     var handlerTypes = new Type[_applicationEventHandlers.Keys.Count];
     _applicationEventHandlers.Keys.CopyTo(handlerTypes, 0);

     foreach (Type handlerType in handlerTypes)
     {
         Delegate[] delegates = _applicationEventHandlers[handlerType].GetInvocationList();
         foreach (Delegate @delegate1 in delegates)
         {
             var handlerToRemove = Delegate.Remove(_applicationEventHandlers[handlerType], @delegate1);
             if (handlerToRemove == null)
             {
                 _applicationEventHandlers.Remove(handlerType);
             }
             else
             {
                 _applicationEventHandlers[handlerType] = handlerToRemove;
             }
         }
     }
 }
That is it for part one, we have created the `ApplicationEventDispatcher`. In part two we will look at implementing it in a Windows Forms application.

Full code available here at My EventDispatcher GitHub repository

Wednesday, February 10, 2016 5:55:00 AM (GMT Standard Time, UTC+00:00)  #    Comments [0] -
.Net | C# | CodeProject | Event Dispatcher | Event Handling | Events
 Thursday, December 24, 2015

Simple Javascript Slickgrid Primer Extension that creates a Simple Searchable Grid.

This article contains code for a simple javascript primer for Slickgrid that creates a simple searchable grid in only a few lines of code.

Any of you who know Slickgrid may be impressed with how it handles large volumes of data. This is especially useful if you write software for a business which is reluctant to move from Excel based applications into web based applications. if you use it regularly then I expect you are producing reams of the same boiler plate code to set it up. We certainly have been. So for this reason I'd like to share with you a small Slickgrid extension that will help with just that. Assuming you have the following basic html with a textbox with an ID of `GridSearch`, a div with the ID of `GridContainer` and a suitable JSON array (called `myFunkyJsonData` in this case), then you can create a SlickGrid with my extension and the following lines of code.

var gridBuilderOptions = {
    data: myFunkyJsonData,
    gridSelector: "#GridContainer",
    searchTextFieldSelector: "#GridSearch",
    searchableColumnName: "SearchField"
};

var gridBuilder = new Slick.SimpleFilteredGridBuilder(gridBuilderOptions );
gridBuilder.withColumn({ name: "Field 1", field: "Field1", minWidth: 100, maxWidth: 200 });
gridBuilder.withColumn({ name: "Field 2", field: "Field2");
gridBuilder.withColumn({ name: "Field 3", field: "Field3", formatter: Slick.Formatters.YesNo);
gridBuilder.withColumn({ name: "SearchField", field: "SearchField", maxWidth: 1);
gridBuilder.buildGrid();
this.grid = gridBuilder.getGrid();
First you need to set up some grid builder options which contain the grid data, a selector for the search text input, the selector for the grid container div and the name of the field to search on. Then initialise the builder with these options. User the `withColumn` method to add column information or use `withAutoColumns` to just use the properties of the data rows to automatically create the columns. next build the grid with `buildGrid`, and get a reference to it with `getGrid` if you need it.

So what code makes all this possible? Well this little extension below.
/*
/   File:       slickgrid.extensions.js
/    Requires:   JQuery
/               SlickGrid
*/

(function ($) {
    // Register namespace
    $.extend(true, window, {
        "Slick": {
            "SimpleFilteredGridBuilder": SimpleFilteredGridBuilder
        }
    });

    function SimpleFilteredGridBuilder(options) {
        var _grid;
        var _columns = [];
        var _gridOptions;
        var _filter;
        var _dataView;
        var _$searchTextField;
        var _searchString;
        var _guard = new Guard();
        var _options;
        var defaults = {
            data: [],
            gridSelector: "",
            searchTextFieldSelector: "",
            searchableColumnName: ""
        };

        guardArguments(options);
        setOptions(options);

        function guardArguments(options) {
            _guard.argumentNotNullOrUndefined(options.data, "data");
            _guard.argumentNotNullUndefinedOrEmpty(options.gridSelector, "gridSelector");
            _guard.argumentNotNullUndefinedOrEmpty(options.searchTextFieldSelector, "searchTextFieldSelector");
            _guard.argumentNotNullUndefinedOrEmpty(options.searchableColumnName, "searchableColumnName");    
        }

        function setOptions(options) {
            _options = $.extend(defaults, options);
        }

        function buildGrid() {
            /// <summary>
            /// Builds the grid, once columns have been set. 
            /// </summary>
            try {
                initSearchField();
                initFilter();
                initDataView();
                initGridOptions();
                initGrid();                
            } catch (e) {
                throw new Error("Failed to initialise grid. " + e.message);
            } 
        }

        function initSearchField() {
            _$searchTextField = $(_options.searchTextFieldSelector);

            if (!textSearchFieldExists)
                throw new Error("No text search field exists for selector '" + searchTextFieldSelector + "'");

            bindSearchFieldHandlers();
            setSearchStringFromField();
        }

        function textSearchFieldExists() {
            return _$searchTextField.length > 0;
        }

        function bindSearchFieldHandlers() {
            _$searchTextField.on("keyup change", filterDataView);
        }

        function setSearchStringFromField() {
            _searchString = _$searchTextField.val().toLowerCase();
        }

        function withAutoColumns() {
            /// <summary>
            /// Automatically creates columns from the properties of the
            /// first row object. Existing columns will be cleared first!
            /// </summary>
            var data = _options.data;
            var noData = (data.length === 0);
            if (noData) throw new Error("Cannot create auto columns when no data is present! ");

            clearColumns();
            var firstRow = data[0];
            createColumnsFromRow(firstRow);
            
        }

        function createColumnsFromRow(row) {
            for (var property in row) {
                if (row.hasOwnProperty(property)) {
                    var column = createColumnFromProperty(property);
                    withColumn(column);
                }
            }
        }

        function createColumnFromProperty(property) {
            return {
                name: property,
                field: property,
                id: property
            }
        }

        function clearColumns() {
            /// <summary>
            /// Clears the columns array. Only need to be called if columns have been
            /// added with `withColumn(column)`
            /// </summary>
            _columns = [];
        }

        function withColumn(column) {
            /// <summary>
            /// Adds the specified column to the columns array. 
            /// </summary>
            if (column == null) throw new Error("column must not be null. ");

            _columns.push(column);
        }

        function getGrid() {
            /// <summary>
            /// Returns a reference to the grid. 
            /// </summary>
            return _grid;
        }

        function filterDataView() {
            _searchString = $(this).val().toLowerCase();
            _dataView.refresh();
        }

        function initFilter() {
            _filter = filter;
        }

        function initDataView() {
            var dataView = new Slick.Data.DataView();
            dataView.beginUpdate();
            dataView.setItems(_options.data, _options.searchableColumnName);
            dataView.setFilterArgs({ searchString: _searchString });
            dataView.setFilter(_filter);
            dataView.endUpdate();

            dataView.onRowCountChanged.subscribe(onRowCountChanged);
            dataView.onRowsChanged.subscribe(onRowsChanged);

            _dataView = dataView;
        }

        function onRowsChanged(e, args) {
            _grid.invalidateRows(args.rows);
            _grid.render();
        }

        function onRowCountChanged() {
            _grid.updateRowCount();
            _grid.render();
        }

        function initGridOptions() {
            _gridOptions = {
                forceFitColumns: true,
                autoSizeColumns: true,
                enableColumnReorder: false
            }  
        }

        function initGrid() {
            ensureColumnsExist();

            _grid = new Slick.Grid(
                _options.gridSelector,
                _dataView,
                _columns,
                _gridOptions);
            _grid.render();
        }

        function ensureColumnsExist() {
            if (_columns.length === 0) {
                throw new Error("Columns must be set before building the grid. " +
                    "Please use the 'withColumn(column)' function to add each column. ");
            }
        }

        function filter(item) {
            var filterCriteriaMet = true;
            var searchableString = item[_options.searchableColumnName].toLowerCase();
            var orSearchDelimiter = ",";
            var andSearchDelimiter = " ";
            var searchString = _searchString;
            var performOrSearch = (searchString.indexOf(orSearchDelimiter) !== -1);
            var performAndSearch = (searchString.indexOf(andSearchDelimiter) !== -1);

            if (performOrSearch) {
                //Option A - When the search string includes a comma, treat it as an OR search, e.g. expand the results
                filterCriteriaMet = orSearchHasMatch(searchableString);
                
            } else if (performAndSearch) {
                // Option B - When the search string includes a space, treat it as an AND search, e.g. restrict the results
                filterCriteriaMet = andSearchHasMatch(searchableString);

            } else {
                // Option C - Single word exact match
                var singleWordExactMatchNotFound = (searchString !== "" && searchableString.indexOf(searchString) === -1);
                if (singleWordExactMatchNotFound) filterCriteriaMet = false;
            }

            return filterCriteriaMet;
        }

        function orSearchHasMatch(searchableString) {

            var matchFound = false;
            var searchStringItems = _searchString.split(",");

            for (var index = 0; index < searchStringItems.length; index++) {
                var trimmedItem = searchStringItems[index].trim();
                var notEmpty = trimmedItem !== "";
                var searchableStringContainsTrimmedItem = (searchableString.indexOf(trimmedItem) !== -1);

                var matchOnTrimmedOrItemNotMet = (notEmpty && searchableStringContainsTrimmedItem);
                if (matchOnTrimmedOrItemNotMet) {
                    matchFound = true;
                    break;
                }
            }
            return matchFound;
        }

        function andSearchHasMatch(searchableString) {
            
            var filterCriteriaMet = true;
            var searchStringItems = _searchString.split(" ");

            for (var index = 0; index < searchStringItems.length; index++) {
                var trimmedItem = searchStringItems[index].trim();
                var notEmpty = trimmedItem !== "";
                var searchableStringDoesnotContainTrimmedItem = (searchableString.indexOf(trimmedItem) === -1);
                
                var matchOnTrimmedAndItemNotMet = (notEmpty && searchableStringDoesnotContainTrimmedItem);
                if (matchOnTrimmedAndItemNotMet) {
                    filterCriteriaMet = false;
                    break;
                }
            }
            return filterCriteriaMet;
        }

        
        return {    // public interface
            clearColumns: clearColumns,
            buildGrid: buildGrid,
            getGrid: getGrid,
            withColumn: withColumn,
            withAutoColumns: withAutoColumns
        }
    }

    function Guard() {
        var argumentNotNullOrUndefined = function (value, argumentName) {
            if (value === undefined || value === null)
                throw new Error("[" + argumentName + "] must not be null or undefined! ");
        }
        var argumentNotNullUndefinedOrEmpty = function (value, argumentName) {
            if (value === undefined || value === null ||
                (value.length && value.length === 0))
                throw new Error("[" + argumentName + "] must not be null, undefined or empty! ");
        }

        return {    // public interface
            argumentNotNullOrUndefined: argumentNotNullOrUndefined,
            argumentNotNullUndefinedOrEmpty: argumentNotNullUndefinedOrEmpty
        };
    }


})(jQuery);
Please note the extension requires Slickgrid and JQuery. See my gist for the full code. I hope that helps someone!

Thursday, December 24, 2015 7:26:08 AM (GMT Standard Time, UTC+00:00)  #    Comments [0] -
CodeProject | Extensions | JavaScript | slickgrid
 Tuesday, December 15, 2015

A quick C# helper class to ensure variable is not null but still use it in the same line


How often do you need to check if a variable is not null and if it is throw an exception, but if it is not then use it straight away? If you are anything like me then it may be quite a lot. For instance there are many .Net framework functions that you may wish to use that will return a null if an object is not found, so this means you will need to check for null before assuming you can use it. An example of this is the `GetConstructor(...)` method on the `PropertyInfo` object - this can return `null`.

var constructorInfo = propertyInfo.PropertyType.GetConstructor(new[] { contextType });
if (constructorInfo == null) throw new InvalidOperationException("No constructor found");
Also if you have class inheritance and you wish to validate the constructor arguments before passing on to the base class you will find there is no easy way to do this in .Net. Take the example below:
public StoredProcedureExecuter(IDbConnection connection, string procedureName)
    : base(connection, procedureName) // How can i validate these?
{
}
So what can you do? Well you can just roll your own function to ensure the value of an argument or variable is valid and then return it so it can be used. So lets give it a go, but before we can so this we need to define a static class to hold the function we will create.

namespace Dibware.Helpers.Validation
{
    /// <summary>
    /// encapsualtes logic to ensures arguments pass specific validation.
    /// </summary>
    public static class Ensure<T>
        where T : class
    {

    }
}
So as you can see this class is a generic and takes a TypeParameter `T` which will be `Type` of both the parameter and the return type. If you want to validate value types then you will need to remove the `class` constraint on the TypeParameter. So now we can add the function to the class.

/// <summary>
/// Checks if the specified arguments the is not null.
/// If it is throws a System.ArgumentNullException
/// </summary>
/// <example>Guard.ArgumentIsNotNull(arg1, "arg1");</example>
/// <param name="value">The value.</param>
/// <param name="argumentName">The argument name.</param>
/// <returns>The argument being checked if argument was not null.</returns>
/// <exception cref="ArgumentNullException"></exception>
[DebuggerHidden]    //Does not appear at all in the call stack
public static T ArgumentIsNotNull(T value, String argumentName)
{
    if (value == null)
    {
        throw new ArgumentNullException(argumentName);
    }
    return value;
}
This function takes an argument called value` which is of type `T`. If checks the value is null, and if it is an exception is thrown with the argument name. If the argument passed validation it is returned so the calling code can carry on and use it. Two example of how this might look if used in a inherited constructor is shown below.
public StoredProcedureExecuter(IDbConnection connection, string procedureName)
    : base(
        Ensure<IDbConnection>.ArgumentIsNotNull(connection, "connection"), 
        Ensure.ArgumentIsNotNullOrWhiteSpace(procedureName, "procedureName"))
{
}
 I have included a couple of unit tests for further reference.

namespace Dibware.Helpers.Tests.Validation
{
    [TestClass]
    public class EnsureofTTests
    {
        #region ArgumentIsNotNull

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Test_ArgumentIsNotNull_WithNullArgument_ThrowsArgumentNullException()
        {
            // Arrange

            // Action
            Ensure<string>.ArgumentIsNotNull(null, "argument");

            // Assert
        }

        [TestMethod]
        public void Test_ArgumentIsNotNull_WithOutNullArgument_ReturnsOriginalValue()
        {
            // Arrange
            const string argument = "TestValue";

            // Action
            var actual = Ensure<string>.ArgumentIsNotNull(argument, "argument");

            // Assert
            Assert.AreEqual(argument, actual);
        }

        #endregion
    }
}
You can extend the `Ensure` helper class to contain more helper functions. Hopefully you may find this little helper pattern useful.

Full Source Code

The full source code can be found at my helpers GitHub repository.

Tuesday, December 15, 2015 7:18:44 PM (GMT Standard Time, UTC+00:00)  #    Comments [0] -
.Net | C# | Helpers | Validation
 Tuesday, December 08, 2015

A behavioural difference between IEnumerable<T>.ForEach and List<T>.ForEach

This morning at work a colleague and myself discovered a slightly alarming difference between `IEnumerable<T>.ForEach` and `List<T>.ForEach` when we switched a collection type for a field in a C# ASP.NET MVC project. Initially the field type had been `IEnumerable<SomeType>` and we had been calling the `.ForEach` on it passing in an action. This was using the `IEnumerable<T>.ForEach` which is defined in the `WebGrease.Css.Extensions` namespace as we have WebGrease already referenced. When we switched the field type to `List<SomeType>` this line auto resolved (maybe via Resharper) to the `.ForEach` within the `System.Collections.Generic` namespace. We started getting some null reference exceptions in our tests.

What we found was for most code-paths this was not a problem, however when the field was null because the WebGrease version is an extension method (see code below) the method checks for null and then just bugs out of the method with no action being taken.

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ListExtensions.cs" company="Microsoft">
//   Copyright Microsoft Corporation, all rights reserved
// </copyright>
// <summary>
//   ListExtensions Class - Provides the extension on List
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace WebGrease.Css.Extensions
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;

    /// <summary>ListExtensions Class - Provides the extension on List</summary>
    public static class ListExtensions
    {
    
        /// <summary>For each extension method for IEnumerable</summary>
        /// <typeparam name="T">The type of items in collection</typeparam>
        /// <param name="list">The list of items</param>
        /// <param name="action">The action to perform on items</param>
        public static void ForEach<T>(this IEnumerable<T> list, Action<T> action)
        {
            if (list == null || action == null)
            {
                return;
            }

            foreach (var item in list)
            {
                action(item);
            }
        }
    }
}


In the List<T> version in `System.Collections.Generic` the ForEach is an instance method so there is no way the method can be called on a null instance, hence the null reference exception. The decompiled code for the method is below.

namespace System.Collections.Generic
{
    /// <summary>Represents a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists.</summary>
    /// <filterpriority>1</filterpriority>
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>))]
    [Serializable]
    public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
    {

        /// <summary>Performs the specified action on each element of the <see cref="T:System.Collections.Generic.List`1"></see>.</summary>
        /// <param name="action">The <see cref="T:System.Action`1"></see> delegate to perform on each element of the <see cref="T:System.Collections.Generic.List`1"></see>.</param>
        /// <exception cref="T:System.ArgumentNullException">action is null.</exception>
        public void ForEach(Action<T> action)
        {
            if (action == null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
            }
            for (int i = 0; i < this._size; i++)
            {
                action(this._items[i]);
            }
        }
    }
}

So my beef here is with the authors of the WebGrease assembly. If they had maybe put a guard on the `list` parameter to throw an exception so it follows closer to the List<T> implementation, rather than just "gracefully" eating the issue and carrying on with no action taken, we would have guarded against a null list ourselves a bit earlier in the development. Or maybe my gripe is more with the fact that extension methods can be called on a `null` object!

I'm now left wondering if any of my other software maybe out there with a little time-bomb like this waiting to blow...
Tuesday, December 08, 2015 12:53:39 PM (GMT Standard Time, UTC+00:00)  #    Comments [0] -
.Net | C# | CodeProject | Extension Methods | Extensions | ForEach | Generics | WebGrease
Categories
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Archive
<February 2016>
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
282912345
6789101112
Blogroll
 DAVID HEINEMEIER HANSSON
Creator of ruby on Rails
 Harry Pierson
Passion * Technology * Ruthless Competence
 Joshua Flanagan
A .NET Software Developer
 Martin Ffowler
Author, speaker, and loud-mouth on the design of enterprise software
 Michael Schwarz's Blog
Developing applications on the Microsoft platform since Windows 3.1!
 Scot GU
Scott Guthrie lives in Seattle and builds a few products for Microsoft
 Scott Hanselman
Programming Life and the Zen of Computers
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2016
Duane Wingett
Sign In
Statistics
Total Posts: 79
This Year: 1
This Month: 1
This Week: 1
Comments: 61
Themes
Pick a theme:
All Content © 2016, Duane Wingett
DasBlog theme 'Business' created by Christoph De Baene (delarou)