user726720
user726720

Reputation: 1247

Returning Generic List from Generic method

I need to return a genericList templateFields as below from a generic list with code as below:

public interface TestData
{
    string field { get; set; }
    string fieldName { get; set; }
    string type { get; set; }
}

private static IList<T> GETCG<T>(string test, string type) where T : Program.TestData
{
    XmlNodeList extractNode = xdoc.SelectNodes(
       @".//mediaInstances/mediaInstance/properties/templateFields/templateField", manager);
    var nodees = new List<XmlNode>(extractNode.Cast<XmlNode>());
    var templateFields = nodees.Cast<XmlNode>().Select(x => new
    {
        field = (String)x.Attributes["userName"].Value,
        fieldName = (String)x.Attributes["name"].Value
            .Substring(0, x.Attributes["name"].Value.IndexOf(':')),
        type = (String)x.Attributes["name"].Value.Substring(x.Attributes["name"].Value
            .IndexOf(':') + 1, 4)                          
    }).ToList();
}

return (T)Convert.ChangeType(templateFields, typeof(T));

I get the following error, on the return:

Object must implement Iconvertible.

I do understand templateFields doesnot implement IConvertible to use ChangeType. What's the best way of returning templateFields

Upvotes: 0

Views: 521

Answers (3)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112762

You declared an interface TestData but didn't declare any type implementing it. You cannot cast any type that just happens to have the same properties by accident to this interface. You must create a class or struct implementing it. Also, with the usual .NET naming conventions interface names start with an upper case I and property names have PascalCase.

With these declarations ...

public interface ITestData
{
    string Field { get; set; }
    string FieldName { get; set; }
    string Type { get; set; }
}

public class TestData : ITestData
{
    public string Field { get; set; }
    public string FieldName { get; set; }
    public string Type { get; set; }
}

You can write

private static IList<ITestData> GETCG(string test, string type)
{
    XmlNodeList extractNode = xdoc.SelectNodes(
        @".//mediaInstances/mediaInstance/properties/templateFields/templateField", manager);
    var nodees = new List<XmlNode>(extractNode.Cast<XmlNode>());
    var templateFields = nodees.Cast<XmlNode>().Select(x => (ITestData)new TestData {
        Field = (String)x.Attributes["userName"].Value,
        FieldName = (String)x.Attributes["name"].Value
            .Substring(0, x.Attributes["name"].Value.IndexOf(':')),
        Type = (String)x.Attributes["name"].Value.Substring(x.Attributes["name"].Value
            .IndexOf(':') + 1, 4)
    }).ToList();
    return templateFields;
}

Note that the method is not generic. To make .ToList() create a IList<ITestData>, the new data must be casted to the interface (ITestData)new TestData { ... }.

The question is whether you still need the interface, or if you prefer to use the class directly.


If you still want the method to be generic, you must tell it that T must have a default constructor with the new() constraint. And you must call the method with a concrete type. I.e., you cannot call it with the interface, since this one does not have a constructor.

private static IList<T> GETCG<T>(string test, string type)
    where T : ITestData, new()
{
    ...
    var templateFields = nodees.Cast<XmlNode>().Select(x => new T {
       ...
    }).ToList();
    return templateFields;
}

and call with

IList<TestData> var result = GETCG<TestData>("hello", "world");

Upvotes: 0

Rufus L
Rufus L

Reputation: 37070

I think the problem here is that you're selecting an anonymous type when you do select new { ... }, and then the Convert.ChangeType fails because anonymous types only include public read-only properties, and don't implement IConvertible. Instead, we want to select a new T. But in order to do this, we also have to include a new() constraint on T, which means that T must have a default constructor (so we can create an instance of it).

By doing this, we don't need to convert anything, as we have a List<T> as a result of the Select.

You can also reduce some code by selecting an IEnumerable<XmlNode> in one line, rather than creating a second variable and doing a cast on the first one.

Something like this should work:

private static IList<T> GETCG<T>(string test, string type) where T : TestData, new()
{
    IEnumerable<XmlNode> templateFieldNodes = xdoc
        .SelectNodes(".//mediaInstances/mediaInstance/properties/templateFields/templateField", 
            manager)
        .Cast<XmlNode>();

    return templateFieldNodes.Select(x => new T
    {
        field = (String)x.Attributes["userName"].Value,
        fieldName = (String)x.Attributes["name"].Value
            .Substring(0, x.Attributes["name"].Value.IndexOf(':')),
        type = (String)x.Attributes["name"].Value.Substring(x.Attributes["name"].Value
            .IndexOf(':') + 1, 4)
    }).ToList();
}

Upvotes: 0

Alexander
Alexander

Reputation: 9642

Add new() contraint to T and use the following codee

private static IList<T> GETCG<T>(string test, string type) where T : TestData, new()
{
    XmlNodeList extractNode = xdoc.SelectNodes(@".//mediaInstances/mediaInstance/properties/templateFields/templateField", manager);
    var nodees = new List<XmlNode>(extractNode.Cast<XmlNode>());
    var templateFields = nodees.Cast<XmlNode>().Select(x => new T() //not anonymous type but T object
    {
        field = x.Attributes["userName"].Value,
        fieldName = (string)x.Attributes["name"].Value.Substring(0, x.Attributes["name"].Value.IndexOf(':')),
        type = x.Attributes["name"].Value.Substring(x.Attributes["name"].Value.IndexOf(':') + 1, 4)

    }).ToList();

    return templateFields;
}

Upvotes: 1

Related Questions