0
class Employee
{
    public string Name {get;set;}
    public int employee_id {get;set}
    public int Age {get;set}
}

Class EmployeeCollection : IEnumerable, IEnumerable<Employee>, IOrderedEnumerable<Employee>
{
    public Expression<Func<Employee, dynamic>> SortOrder {get;set;}
    protected Dictionary<int,Employee> EmployeeById = new Dictionary<int,Employee>();
    public void AddEmployee(Employee ePassed)
    {
        EmployeeById[ePassed.employee_id] = ePassed;
    }
    public IEnumerator<Employee> GetEnumerator()
    {
      foreach (int id in EmployeeByID.Keys)
      {
        yield return EmployeeById[id];
      }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
      return this.GetEnumerator();
    }
    public IOrderedEnumerable<Employee> CreateOrderedEnumerable<TKey>(Func<Employee, TKey> KeySelector, IComparer<TKey> comparer, bool descending)
    {
      if (descending)
          return this.OrderByDescending(KeySelector, comparer);
      else
          return this.OrderBy(KeySelector, comparer);
    }
    public IEnumerable<Employee> OrderedObjects
    {
        if (this.SortOrder == null)
            return (IEnumerable<Employee>)this;  // No Sort Order applied
        else
        {
          // How do I get the "parameters" from SortOrder to pass into CreateOrderedEnumerable?
          throw new NotImplementedException();
        }
    }
}

I want to be able to use syntax similar to the following...

EmployeeCollection.SortOrder = (x => x.Name);
foreach (Employee e in EmployeeCollection.OrderedObjects)
{
  // Do something in the selected order
}

There are thousands of examples of how to throw sorted, filtered, etc results into a new List, Collection, ObservableCollection, etc. but if your existing collection already responds to events, automatically adds new objects in response to notifications, user actions, new data coming in from server, etc then all that functionality is either "lost" or has to be "added" to make the new List, Collection, ObservableCollection, etc. listen to the original collection in order to somehow stay in sync with all of the various updates, properties, etc that the original Collection ALREADY KNOWS ABOUT and handles... I want to be able to have the ORIGINAL "EmployeeCollection" simply dish out the "Employee" objects in the requested SortOrder...

I made a "wild ass guess" about the syntax for the "SortOrder" property based on wanting to make the syntax of the SortOrder property similar to the orderby portion of lambda expressions that other developers on the team are used to working with by looking at the extension methods in System.Linq.Enumerable similar to the following: public static IOrderedEnumerable<TSource> OrderBy<ToSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);

I am new to Linq, lambda, etc and apologize in advance if I somehow missed some key aspect of Linq/Expression Trees,Predicates,anonymous delegates, etc that others consider obvious.

Tim S.
  • 55,448
  • 7
  • 96
  • 122
Wonderbird
  • 392
  • 4
  • 12
  • 2
    This is exactly what `EmployeeCollection.OrderBy(...)` would do and with a more convenient syntax to boot. What's wrong with doing that? – Jon Oct 24 '13 at 20:48
  • The problem with doing that is that the OrderBy() extension method returns IEnumerable which knows absolutely NOTHING about the properties of the original EmployeeCollection, is not hooked up to the INotifyCollectionChanged interface that EmployeeCollection implements, etc, etc, etc... (You basically get back a "dumb list of objects" that doesn't know how to do ANY of the things that the original collection could... – Wonderbird Oct 24 '13 at 22:14
  • So? The original collection still knows. If something happens to it then you know you have to recalculate the sorted view again. That's more work, but obviously it is impossible to keep any number of sorted views in sync with the "original" for free. You only get a choice between syncing them eagerly (if you know about all of them in advance, which is not at all clear from the q) or lazily, as proposed above. – Jon Oct 24 '13 at 23:06

1 Answers1

1

This works when you don't use ThenBy-type operations (which are all the IOrderedEnumerable<T> interface really adds). See C#: How to implement IOrderedEnumerable<T> for a solution to support that.

public class Employee
{
    public string Name {get;set;}
    public int employee_id {get;set;}
    public int Age {get;set;}
}

public class EmployeeCollection : IEnumerable, IEnumerable<Employee>, IOrderedEnumerable<Employee>
{
    public Func<Employee, object> SortOrder {get;set;}
    public Func<Employee, bool> Filter {get;set;}
    protected Dictionary<int,Employee> EmployeeById = new Dictionary<int,Employee>();
    public void Add(Employee ePassed)
    {
        EmployeeById[ePassed.employee_id] = ePassed;
    }
    public IEnumerator<Employee> GetEnumerator()
    {
        var employees = EmployeeById.Keys.Select(id => this.GetEmployee(id));
        if (Filter != null)
            employees = employees.Where(Filter);
        if (SortOrder != null)
            employees = employees.OrderBy(SortOrder);
        return employees.GetEnumerator();
    }
    public Employee GetEmployee(int id)
    {
        return EmployeeById[id];
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
      return this.GetEnumerator();
    }
    public IOrderedEnumerable<Employee> CreateOrderedEnumerable<TKey>(Func<Employee, TKey> KeySelector, IComparer<TKey> comparer, bool descending)
    {
      throw new NotImplementedException();
    }
}

// this is some code you might use to test this:
var EmployeeCollection = new EmployeeCollection
{
    new Employee { employee_id = 1, Age = 20, Name = "Joe" },
    new Employee { employee_id = 2, Age = 30, Name = "Thomas" },
    new Employee { employee_id = 3, Age = 25, Name = "Robert" },
};
EmployeeCollection.SortOrder = x => x.Age;
EmployeeCollection.Filter = x => x.Name.Length > 4;
foreach (Employee e in EmployeeCollection)
{
    // do something
}
Community
  • 1
  • 1
Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • 1
    @Wonderbird I've added support for `GetEmployee` and `Filter`. – Tim S. Oct 24 '13 at 22:09
  • Thanks to @tim-s but the actual enumerator looks more like the following. (The dictionary holds buffers instead of fully fleshed out objects. The function that returns an "Employee" first checks to see if there is a weak reference, if it is still valid or not, creates or recreates the actual Employee instance if needed before returning it.. public IEnumerator GetEnumerator() { foreach (int id in EmployeeById.Keys) { Employee e = this.GetEmployee(id); if (this.Filter != null) { if (this.Filter(e)) yield return e; } else yield return e; } } – Wonderbird Oct 24 '13 at 22:17
  • Your example (updated) definitely answers my original question(s)/request. However I wish I had a way to apply the Filter object by object. (Employee is really EmployeeBase with EmployeeSecret and EmployeePublic classes that inherit from it.) EmployeeCollectionBase similarly has EmployeeCollectionSecret and EmployeeCollectionPublic that inherit from it. Trying to honor base.SortOrder / base.Filter of EmployeeCollectionBase if EmployeeCollectionSecret does not have its own SortOrder/Filter defined sends me into "typecasting hell." – Wonderbird Oct 24 '13 at 23:37
  • Would it help to make it generic, `EmployeeCollection where T : EmployeeBase`? Then the `Func` types become `Func`, and you don't need to define a different one on your subclasses, and this should all become simple again.. – Tim S. Oct 25 '13 at 11:51