2

Hi I'm not sure a generic method is the right way to solve my problem. I need to parse an XML file and read items from it. Items can be things like orderLines, notes, attachments. The basic steps to get these items are all the same. How can I make 1 method which creates a list of these items and call a specific method to read an item?

    public override IList<T> GetItems<T>(XPathNavigator currentOrder) where T : ISortableByLineNumber, new ()
    {
        var itemList = new List<T>();
        var itemXmlNodes = currentOrder.Select(OrderXPath);
        if (itemXmlNodes == null)
            throw new Exception("");
        var lineNumber = 1;
        foreach (XPathNavigator itemXmlNode in itemXmlNodes)
        {
            var item = new T();
            item = ReadItem(itemXmlNode, lineNumber++, item);
            itemList.Add(item);
            Logger.Debug(string.Format("Added item {0}", item));
        }
        return itemList;
    }

I thought I could do this with the ReadItem method. I would create overloads for each type of item I would be reading.

    private ISortableByLineNumber ReadItem(XPathNavigator itemXmlNode, int i, OrderLine item)
    {
        // specific code to read a orderLine
    }

    private ISortableByLineNumber ReadItem(XPathNavigator itemXmlNode, int i, Note item)
    {
        // specific code to read a note
    }

But when I try to compile this could I get "The best overloaded method match for 'XmlOrderParser.XmlOrders.Prs3XmlFileWithOrders.ReadItem(System.Xml.XPath.XPathNavigator, int, XmlOrderParser.Entities.OrderLine)' has some invalid arguments". The problem is the compiler doesn't know how to cast T to OrderLine or Note.

Martijn de Munnik
  • 924
  • 12
  • 23
  • Does your classes: OrderLine, Note and other ones implement the interface ISortableByLineNumber? – S2S2 Jan 23 '12 at 12:21
  • Yes, they all implement that interface. I'm not calling the function yet, so it's a compile time error, not a run time error. – Martijn de Munnik Jan 23 '12 at 12:25
  • then I suppose you should use the interface types as opposed to concrete types in your method params, I have posted an answer for this.. – S2S2 Jan 23 '12 at 12:32

3 Answers3

2

If you are using .NET 4 you can make use of the new dynamic type by changing only one thing:

dynamic item = new T(); // instead of var item = new T();

Because item now is dynamic the runtime does an automatic overload resolution based on the actual type of item.
Please be aware that you will receive a runtime exception if T is a type for which no overload exists.


The following snippet demonstrates your problem (paste into LINQPad and choose "C# program" as language):

void Main()
{
    Method<Class1>(); // Outputs Class1
    Method<Class2>(); // Outputs Class2
    Method<Class2b>(); // Outputs Class2, because it falls back to the base type
    Method<Class3>(); // Throws exception
}

void Method<T>() where T : new()
{
    dynamic c = new T();
    Method(c);
}

void Method(Class1 c) { Console.WriteLine("Class1"); }
void Method(Class2 c) { Console.WriteLine("Class2"); }

class Class1 {}
class Class2 {}
class Class2b : Class2 {}
class Class3 {}
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • +1, although I would add more information on why we need to defer it to runtime, namely the fact the compiler has no knowledge on whether `T` in `GetItems` is any of `OrderLine` or `Note`, etc., because no generic constraint says so. – Krizz Jan 23 '12 at 12:43
0

That's because at compile time you don't know the method you are going to need. To solve this, you need to find the right method and invoke it. Try something like:

MethodInfo mi = typeof(YourClass).GetMethod("ReadItem",
    BindingFlags.NonPublic,
    null,
    new Type[] { typeof(XPathNavigator), typeof(int), typeof(T) },
    null);
item = mi.Invoke(this, new object { itemXmlNode, lineNumber++, item });

Hope it helps. Good luck!

EPLKleijntjens
  • 916
  • 1
  • 8
  • 21
  • 1
    This is more or less a manual transcription of what `dynamic` does. Nice substitute for Daniel's answer if using C# < 4.0, although there might be problem if T is a subclass of the argument type of some overload, or am I wrong? – Krizz Jan 23 '12 at 12:45
0

As you told in comment, your all classes OrderLine, Note etc. implement the ISortableByLineNumber interface, so you can change the definition of your method ReadItem to:

private OrderLine ReadItem(XPathNavigator itemXmlNode, int i, ISortableByLineNumber item)
{

}

and the actual type which is passed to the item param in above method would have its specific implementation executed at runtime, so if item is of type OrderLine when passed from GetItems<T> method then its specific implementation would be invoked and so on..

The above change in method definition would make your method definition interface specific and not concrete type specific which is one of the practices of interface based programming explained in below links:

http://visualstudiomagazine.com/articles/2010/01/01/interface-based-programming.aspx

What exactly is "interface based programming"?

Community
  • 1
  • 1
S2S2
  • 8,322
  • 5
  • 37
  • 65