简体   繁体   中英

How do I make the method/s generic which returns different types, but accepts the same type of instance?

I have multiple methods which reads data from XML having the following format:

<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
<document>
    <employee>
        <name>
            <lastname>Sample</lastname>
            <firstname>Test</firstname>
        </name>
        <professionalDetails>
            <hiredate>October 15, 2016</hiredate>
            <designation>Clerk</designation>
        </professionalDetails>
        <projects>
            <project>
                <product>Printer</product>
                <id>111</id>
                <price>$111.00</price>
            </project>
            <project>
                <product>Laptop</product>
                <id>222</id>
                <price>$989.00</price>
            </project>
        </projects>
    </employee>
</document>

To read the above data, I have the following methods with their respective classes.

  • Please note that I have my own implementation of " GetElementAsString " method. Bear with me for not providing the implementation of that.

Methods:

    private static NameDetails GetsNameDetails(XNode content)
    {
        var element = content.XPathSelectElement("document/employee/name");

        return new NameDetails
        {
            FirstName = element.GetElementAsString("firstName"),
            LastName = element.GetElementAsString("lastName")
        };
    }

    private static ProfessionalDetails GetsProfessionalDetailsDetails(XNode content)
    {
        var element = content.XPathSelectElement("document/employee/professionalDetails");

        return new ProfessionalDetails
        {
            HireDate = element.GetElementAsString("hiredate"),
            Designation = element.GetElementAsString("designation")
        };
    }

    private static Projects GetsProjectDetails(XNode content)
    {
        var element = content.XPathSelectElement("document/employee/projects/project");

        return new Projects
        {
            Id = element.GetElementAsString("id"),
            Price = element.GetElementAsString("price"),
            Product = element.GetElementAsString("product")
        };
    }
}

internal class Projects
{
    public int Id { get; set; }
    public string Product { get; set; }
    public string Price { get; set; }
}

internal class ProfessionalDetails
{
    public DateTime HireDate { get; set; }
    public string Designation { get; set; }
}

internal class NameDetails
{
    public string FirstName  { get; set; }
    public string LastName { get; set; }
}

The underlying logic is the same for both methods. The initializing type is the only thing that gets changed. The return value of the method and the properties/fields to be initialized change, but the parameter remains the same.

How do I have one single Generic method for the below methods and decide the type to be initialised at run-time?

You could write something like this:

private static T ThingFiller<T>(
    XNode context, 
    string elementStr,
    params Action<Func<string, string>, T>[] setters
)
    where T : new()
{
    var element = context.XPathSelectElement(elementStr);
    var t = new T();

    foreach (var setter in setters)
        setter(element.GetElementAsString, t);      

    return t;
}

private static NameDetails GetsNameDetails(XNode content)
{
    return ThingFiller<NameDetails>(content, "document/employee/name",
        (func, nd) => nd.FirstName = func("firstName"),
        (func, nd) => nd.LastName = func("lastName")
    );
}

Alternatively, you could use an XML library and simply define how to fill the fields via attributes and mapping.

You could add a generic type and then depending on the type decide how you want to parse the node:

private static T GetsDetails<T>(XNode content)
{
    if(typeof(T) == typeof(ProjectDetails)){
        var element = content.XPathSelectElement("document/employee/projects/project");

        return (T)new Projects
        {
            Id = element.GetElementAsString("id"),
            Price = element.GetElementAsString("price"),
            Product = element.GetElementAsString("product")
        };
    }
    else if(typeof(T) == typeof(ProfessionalDetails){ ... } 
    else if (...) {...}
}

I wouldn't recommend this approach though: you still have to do type-checking and you gain very little. The initial implementation you have is far simpler.

You could create an attribute which determines which XPath expression should be used. Example:

[XPathSelector("document/employee/projects/project")]
internal class Projects
{
    public int Id { get; set; }
    public string Product { get; set; }
    public string Price { get; set; }
}

and then you could use this via reflection (code is untested, no c# compiler available here):

private static T GetNode<T>(XNode content)
    : where T : new()
{
    var selector = typeof(T).GetCustomProperty<XPathSelector>();
    var element = content.XPathSelectElement(selector.XPathSelector);

    var ret = new T();

    foreach (var child in element.Elements) {
        var property = typeof(T).GetProperties().Single(p => p.Name.ToLower() == child.Name.ToLower());

        property.SetValue(ret, child.GetElementAsString(child.Name));
    }   

    return ret;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM