Using Except() on a Generic collection

13,463

Solution 1

I think the best way would be to define an interface with all of the properties that you want to use in your method. Have the classes that the method may be used in implement this interface. Then, use a generic method and constrain the generic type to derive from the interface.

This example may not do exactly what you want -- it fills in missing dates for items in the list matching a description, but hopefully it will give you the basic idea.

 public interface ITransactable
 {
     string Description { get; }
     DateTime? TransactionDate { get; }
 }

 public class CompletedTransaction : ITransactable
 {
     ...
 }

 // note conversion to extension method
 public static void FillInMissingDates<T>( this IEnumerable<T> collection, 
                                           string match,
                                           DateTime defaultDate )
        where T : ITransactable
 {
      foreach (var trans in collection.Where( t => t.Description = match ))
      {
          if (!trans.TransactionDate.HasValue)
          {
              trans.TransactionDate = defaultDate;
          }
      }
 }

You'll need to Cast your enumeration to ITransactable before invoking (at least until C# 4.0 comes out).

 var list = new List<CompletedTransaction>();

 list.Cast<ITransactable>()
     .FillInMissingDates("description",DateTime.MinValue);

Alternatively, you could investigate using Dynamic LINQ from the VS2008 Samples collection. This would allow you to specify the name of a property if it's not consistent between classes. You'd probably still need to use reflection to set the property, however.

Solution 2

You could try this approach:

public IEnumerable<T> FillInMissingDates<T>(IEnumerable<T> collection, 
    Func<T, DateTime> dateProperty, Func<T, string> descriptionProperty, string desc)
{
    return collection.Except(collection
        .Where(d => descriptionProperty(d) == desc))
        .Select(d => dateProperty(d));
}

This allows you to do things like:

someCollection.FillInMissingDates(o => o.CreatedDate, o => o.Description, "matching");

Note that you don't necessarily need the Except() call, and just have:

.. Where(d => descriptionProperty(d) != desc)
Share:
13,463

Related videos on Youtube

Christopher Mann
Author by

Christopher Mann

Veteran software engineer and database architect. With more than 15 years of experience I have built solutions for a wide variety of organizations; from federal government agencies in Brazil to one of the largest health care systems in the United States. My specialties are .Net, SQL Server, Business Analytics, and user experience in general. Occasional speaker in code camps and user groups, member of the International .Net Association, president/founder of the Central California .Net User Group, Central California Give Camp, board member of the Central Valley Software Partnership in Central California, member of the International Institute of Business Analysis. I have a Bachelor's Degree in Computer Science and an MBA with emphasis in Information Systems and Entrepreneurship from California State University, Fresno. Microsoft Certified IT Professional (MCITP) in SQL Server Programming and Administration, Microsoft Certified Professional in C#, Certified Qlikview Developer and Designer.

Updated on June 04, 2022

Comments

  • Christopher Mann
    Christopher Mann almost 2 years

    I have asked this question about using the a Linq method that returns one object (First, Min, Max, etc) from of a generic collection. I now want to be able to use linq's Except() method and I am not sure how to do it. Perhaps the answer is just in front on me but think I need help.
    I have a generic method that fills in missing dates for a corresponding descriptive field. This method is declared as below:

    public IEnumerable<T> FillInMissingDates<T>(IEnumerable<T> collection, string datePropertyName, string descriptionPropertyName)
        {
           Type type = typeof(T);
           PropertyInfo dateProperty = type.GetProperty(datePropertyName);
           PropertyInfo descriptionProperty = type.GetProperty(descriptionPropertyName);
           ...
        }
    

    What I want to accomplish is this. datePropertyName is the name of the date property I will use to fill in my date gaps (adding default object instances for the dates not already present in the collection). If I were dealing with a non-generic class, I would do something like this:

    foreach (string description in descriptions)
    {
        var missingDates = allDates.Except(originalData.Where(d => d.Description == desc).Select(d => d.TransactionDate).ToList());
    ...
    }
    

    But how can I do the same using the generic method FillInMissingDates with the dateProperty and descriptionProperty properties resolved in runtime?

    • Noldorin
      Noldorin almost 15 years
      You'll need to show us the definition of your generic class (or at least the relevant bits) before we can properly answer.
    • Christopher Mann
      Christopher Mann almost 15 years
      Noldorin, the type is T, as in the FillInMissingDates generic method declaration.
    • Noldorin
      Noldorin almost 15 years
      @Gustavo: I'm not sure what you mean. If you're not restricting the generic type T, then you can't guarantee the existence of the datePropertyName property.
    • Noldorin
      Noldorin almost 15 years
      Sorry, still not quite sure what you mean by "do the same". In the example you give, you're using the Except/Where methods. In what respect do you want to do the same thing?
    • Christopher Mann
      Christopher Mann almost 15 years
      I want to use the same Except/Where methods, but the name of the properties is only known at run-time.
    • Jason
      Jason almost 15 years
      Do you mean that you would like to use syntax like: someCollection.FillInMissingDates(x => x.CreationDate, x => x.ItemDescription) ?
  • Noldorin
    Noldorin almost 15 years
    That's not what he's referring to I think. He means that YourClass is generic, not the Except extension method. He even shows in his example type inference makes this unnecessary in his case.
  • Christopher Mann
    Christopher Mann almost 15 years
    You're right Noldorin. I don't know what is the name of the property Description (thus, the descriptionPropertyName parameter). I've added more information to the question. Thanks.
  • Noldorin
    Noldorin almost 15 years
    This is probably more what he's looking for, though I'm still waiting for clarification.
  • Christopher Mann
    Christopher Mann almost 15 years
    Hi tvanfosson. Thanks for your answer. I would not like to use an interface because may not have the ability to the interface to the class definition. As I explained in the question (after I edited), I am using reflection to get the actual properties based on their names. Thanks!