Wednesday, March 30, 2011

Is there a nicer/inline way of accomplishing the following in C# / LINQ?

Often I find myself filling ASP.NET repeaters with items that need the CSS class set depending on index: 'first' for index 0, 'last' for index (length-1), and 'mid' in the middle:

_repeater.DataSource = from f in foos
           select new
           {
             ...,
          CssClass = MakeCssClass( foos, f )
           };


private static string MakeCssClass( Foo[] foos, Foo f )
{
  var index = Array.IndexOf( foos, f );

  if( index == 0 )
  {
    return "first";
  }
  else if( index == foos.Length - 1 )
  {
    return "last";
  }
  else
  {
    return "mid";
  }
}

Is there a nicer way I can achieve this (eg using lambda functions)? If I try I get CS0828, "Cannot assign lambda expression to anonymous type property".

From stackoverflow
  • You might be interested in my SmartEnumerable type in MiscUtil.

    From the usage page, there's an example:

    using System;
    using System.Collections.Generic;
    
    using MiscUtil.Collections;
    
    class Example
    {
        static void Main(string[] args)
        {
            List<string> list = new List<string>();
            list.Add("a");
            list.Add("b");
            list.Add("c");
            list.Add("d");
            list.Add("e");
    
            foreach (SmartEnumerable<string>.Entry entry in
                     new SmartEnumerable<string>(list))
            {
                Console.WriteLine ("{0,-7} {1} ({2}) {3}",
                                   entry.IsLast  ? "Last ->" : "",
                                   entry.Value,
                                   entry.Index,
                                   entry.IsFirst ? "<- First" : "");
            }
        }
    }
    

    With implicitly typed variables and a bit more type inference the syntax could be tidied up quite easily. I must get round to that some time, but the basics are already there.

    stusmith : Lovely, thanks. I added a helper method to SmartEnumerable.Entry: public V FirstMidLast( V first, V mid, V last ) { if( IsFirst ) { return first; } else if( IsLast ) { return last; } else { return mid; } }
  • Here's a clever way to get those mids - Skip, Reverse, Skip (what is this, UNO?).

    List<SomeClass> myList = foos
      .Select(f => new SomeClass{ ..., CssClass=string.Empty })
      .ToList();
    
    if (myList.Any())
    {        
      myList.First().CssClass = "first";
      myList.Last().CssClass = "last";
      foreach(var z in myList.Skip(1).Reverse().Skip(1))
      {
        z.CssClass = "mid";
      }
    }
    
    _repeater.DataSource = myList;
    

    Here's a better way for this problem statement.

    List<SomeClass> myList = foos
      .Select(f => new SomeClass{ ..., CssClass="mid" })
      .ToList();
    
    if (myList.Any())
    {    
      myList.First().CssClass = "first";
      myList.Last().CssClass = "last";
    }
    
    _repeater.DataSource = myList;
    

    Of course, neither of these technique will work if you are using anonymous types (their properties are read-only). Don't use anonymous types for query results.

    stusmith : I prefer anonymous types, as it gives you a certain amount of compile-time checking between database schema and ASP.NET pages.

0 comments:

Post a Comment