tags:

views:

694

answers:

4

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?

Thanks!

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

In fact, almost all LINQ extension in C# have a generic possible value. (Except and Except)

Timotei Dolean
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.
Noldorin
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.
Gustavo Cavalcanti
A: 

If you're going to identify the property to be accessed by a string name, then you don't need to use generics. Their only purpose is static type safety. Just use reflection to access the property, and make the method work on a non-generic IEnumerable.

Daniel Earwicker
This is probably more what he's looking for, though I'm still waiting for clarification.
Noldorin
+2  A: 

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.

tvanfosson
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!
Gustavo Cavalcanti
+1  A: 

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)
Jason