Greg Young [MVP]

Sponsors

The Lounge

News

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Chain<T>

I have found this class useful so I figured that I would share it. It's main use is a quick lightweight fluent interface for creating an IEnumerable<T> in a test. The place where it excels is when we want to do something like have a nice fluent interface for adding things to another class. A while back I saw an example of this on Jeff's blog.

The particular example was:

 108: [Test]
109: public void ShouldInitializeWithKeys()
110: {
111: SmartBag bag = new SmartBag().Add("key1", 2).Add("key2", 3);
112: Assert.That(bag.ContainsKey("key1"));
113: Assert.That(bag.ContainsKey("key2"));
114: }

What he had done to support this nice fluent interface for adding items was to make the SmartBag return itself from the add method (This pattern is quite common for value objects but in this case SmartBag was actually mutating itself then returning itself). I had a place or two like this in code and changed them to use the Chain<T> class by just making the class with the add support taking an IEnumerable<T> (kill two birds with one stone).

WhatEver.Add(Chain<int>.Create(1,2,3,4,5))

WhatEver.Add(
             Chain<SomeObject>.Empty
                    .Append(New.SomeObject.WithFluentInterface)
                    .Append(New.SomeObject.WithFluentInterface)
                    .Append(New.SomeObject.WithFluentInterface)
                    .Append(New.SomeObject.WithFluentInterface)
                    .Append(New.SomeObject.WithFluentInterface)
);

Personally I find myself using the From method more to initially create them but the append can be useful as well.

This code is also an example of an immutable data structure. You can access it here http://codekeep.net/snippets/6a941ef6-88ff-4903-b0fd-7ca261ef3bd3.aspx or view it below.

 

One thing I have really wanted to add to it is support for adding a node of IEnumerable<T> which would then iterate that node (i.e. so you could take 5 enumerarables and combine them into 1 without actually evaluating them). Maybe I will do that tomorrow.

 

    public class Chain : IEnumerable, IEnumerable
    {
        private static readonly Chain empty = new Chain(default(T), null);
        public static Chain Empty
        {
            get { return empty; }
        }

        public static Chain From(T t)
        {
            return Empty.Append(t);
        }

        public static Chain From(IEnumerable _Items)
        {
            return Empty.Append(_Items);
        }

        public static Chain From(params T[] items)
        {
            return Empty.Append(items);
        }

        private readonly T item;
        private readonly Chain next;

        private IEnumerable GetItemsRecursive()
        {
            if (next != null) {
                foreach (T child in next.GetItemsRecursive()) yield return child;
                yield return item;
            }
        }

        public Chain Append(T t)
        {
            return new Chain(t, this);
        }

        public Chain Append(IEnumerable _Items)
        {
            if (_Items == null) throw new ArgumentNullException("Items");
            Chain current = this;
            foreach (T cur in _Items) {
                current = current.Append(cur);
            }
            return current;
        }

        public Chain Append(params T[] items)
        {
            return Append((IEnumerable) items);
        }

        private Chain(T _Item, Chain _Next)
        {
            item = _Item;
            next = _Next;
        }

        public IEnumerator GetEnumerator()
        {
            return GetItemsRecursive().GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetItemsRecursive().GetEnumerator();
        }
    }

Posted Wed, Apr 2 2008 1:50 AM by Greg

[Advertisement]

Comments

Ayende Rahien wrote re: Chain<T>
on Wed, Apr 2 2008 4:50 AM

Chain<int> c = Chain<int>.Empty;

for(i=0;i<100000;i++)

  c = c.Append(i);

foreach(i in c)//stack overflow, right?

{

}

Mark wrote re: Chain<T>
on Wed, Apr 2 2008 8:07 AM
I think something is missing from the code. I don't see the Create(..) method that you refer to. Did all the code get posted?
Greg wrote re: Chain<T>
on Wed, Apr 2 2008 9:30 AM

Oren: yes you would get a recursion error on that but then again I would never under any circumstances recommend doing that :-) The place where I am using this is for manually inputting test data .. In the scenario you discuss it would be creating 100,001 objects which is also really a bad thing (compared to say an array which would use 2) or using a generator function which would use 1.

Mark: by the create method I mean From

Dew Drop - April 2, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - April 2, 2008 | Alvin Ashcraft's Morning Dew
on Wed, Apr 2 2008 9:44 AM

Pingback from  Dew Drop - April 2, 2008 | Alvin Ashcraft's Morning Dew

Jason Haley wrote Interesting Finds: April 2, 2008
on Wed, Apr 2 2008 10:25 AM
DotNetKicks.com wrote Chain
on Wed, Apr 2 2008 12:41 PM

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Jon Skeet wrote re: Chain<T>
on Wed, Apr 2 2008 2:45 PM
Doesn't the C# 3 collection initializer syntax make this straightforward anyway? That's a simple way of creating a collection with multiple items in a single expression. For example: List items = new List { "first", "second", "third" }; The important bit is that it's a single expression - which is what Chain achieves as well, but I find the C# syntax slightly simpler, and you don't get the nasty recursion issue when iterating. Am I missing something?
Greg wrote re: Chain<T>
on Wed, Apr 2 2008 2:53 PM

Yes Jon it does make it pretty easy to do ... For this project we are still in 2.0 though. I would say that the two have pretty much an equivalent "easiness" though with the From() creation methods.

Cheers,

Greg

Add a Comment

(required)  
(optional)
(required)  
Remember Me?