0

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

Rufus L
  • 36,127
  • 5
  • 30
  • 43
user726720
  • 1,127
  • 7
  • 25
  • 59
  • 2
    GETCG ... where T : Program.TestData... I don't think you need a generic method here. – user78403 Feb 02 '19 at 21:04
  • If I'm to use a generic method, can you please advise how to solve this – user726720 Feb 02 '19 at 21:06
  • Add new() to your where clause then you can select new T {... Your list will then not need converting – Bob Vale Feb 02 '19 at 21:11
  • `select new { .. }` returns a list of anonymous types. Have you tried `select new T { … }`? That will return a `List`, and you won't need the convert statement. – Rufus L Feb 02 '19 at 21:16
  • @RufusL: That's exactly what Alexander has mentioned in his answer. And I'm already casting nodees to XMLNODE – user726720 Feb 02 '19 at 21:33
  • I meant you *don't* need to do `nodees.Cast()`, since the `nodees` items are already `XmlNode` types (you defined `nodees` as a `List`). – Rufus L Feb 02 '19 at 21:51

3 Answers3

1

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;
}
Alexander
  • 9,104
  • 1
  • 17
  • 41
0

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();
}
Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • That's excellent, but can I know how do call it. actually I'm yet to familiarize myself with generic types and methods: var returning = GETCG>(test,type); – user726720 Feb 02 '19 at 21:39
  • Also if you can leave me some links to get more information on generic types and methods, will be grateful – user726720 Feb 02 '19 at 21:39
  • If you search for "C# generics" with your favorite search engine, you should find lots of reading, including [Generics (C# Programming Guide)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/index) – Rufus L Feb 02 '19 at 21:44
  • Thanks for that will do some reading on it. Can you advise in the meanwhile how do I call the method: var returning = GETCG>(test,type); – user726720 Feb 02 '19 at 21:48
  • Replace the `?` with a type that implements `TestData` and has a default constructor. – Rufus L Feb 02 '19 at 21:49
  • `var returning = GETCG(test, type)`, this give me an error `TestData` must be non abstract type. – user726720 Feb 02 '19 at 21:57
  • 1
    It seems you didn't read all the words in my comment. You need to pass a type that [*implements*](https://stackoverflow.com/questions/4146020/what-does-implementing-an-interface-exactly-mean) `TestData` and *has a [default constructor](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/constructors)*. You're passing the `TestData` interface (which really should be named `ITestData`). If you rename your interface to `ITestData`, then create a concrete class that implements it: `class TestData : ITestData`, then pass an instance of *that* class, it should work. – Rufus L Feb 02 '19 at 21:59
0

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");
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188