Sunkas
Sunkas

Reputation: 9590

Generics to generalize WebRequests and parsing into custom objects in C#

A bit of a architectural question. I am doing a lot of WebRequest:s in C# (Mono). Here's an example how one method that fetches "WorkFlowSchemes" are declared:

public void GetWorkFlowSchemes(
   Credientials credentials, 
   ServerDelegates.WorkFlowSchemesSuccessDelegate successDelegate, 
   ServerDelegates.ErrorDelegate errorDelegate)

Which then have "hardcoded" values for which url to fetch from, which callback the response it calling and in that callback: which type to parse to.

I would like to make this method generic to not having to write X number of methods all very similar for each type of call I make. Instead I would like something like this:

public void GetCustomPath<K>(
    Credientials credentials,
    Path pathToFetch,
    ServerDelegate<K>.SuccessDelegate successDelegate, //Generic delegate to return different types of parsed result
    ServerDelegates.ErrorDelegate errorDelegate) // no need to be generic

where K is the type of the delegate to return i.e. WorkFlowScemes or WorkFlow.

This work compiles fairly well, until the parsing. Previously I parsed it by doing this:

List<WorkFlowScheme> workFlowSchemeList = serverXmlParser.parseXmlToWorkFlowSchemeList(responseBody);

yet another hardcoded parsing method for WorkFlowList I would prefer to generalize. The parseXmlToWorkFlowSchemeList looks like this:

    public List<WorkFlowScheme> parseXmlToWorkFlowSchemeList (string xml)
    {
        XDocument doc = XDocument.Parse(xml);
        List<WorkFlowScheme> result = 
            (
                from x in doc.Element("workflowschemeList").Elements("workflowscheme")
                select new WorkFlowScheme().Setup(x)
                ).ToList();
        return result;
    }

where .Setup(x) is a help method stored in each type to convert and store the XElement values into strings in the current object. Here is where I got stuck. First I tried to make a generic method:

public List<T> parseXmlToCustomObject<T> (string xml, String root, String key)
{
        XDocument doc = XDocument.Parse(xml);
        List<T> result = 
            (
                from x in doc.Element(root).Elements(key)
                select new T().Setup(x)
                ).ToList();
        return result;
}

"select new T.Setup(x)" does not work

Any suggestions?

I have also considered making all my objects to parse to a subclass of a "ServerObject" to more easy be able to call .Setup, but can't figure out how to merge that with generics.

Edit: Here's what I got so far:

public void GetCustomListObject<T>(Account account, String path, RequestStateGen<List<T>>.SuccessDelegate successDelegate, ServerDelegates.ErrorDelegate errorDelegate)
        where T : ServerObject, new()
    {
        var request = getGetRequest(account, path);

        RequestStateGen<List<T>> state = new RequestStateGen<List<T>>();
        state.Request = request;

        IAsyncResult result =
            request.BeginGetResponse(new AsyncCallback(onGetWorkFlowSchemes), state);

        //setupTimeOut(result, state);
        string responseBody = extractResponseAndCallDelegateOnError(request, result, errorDelegate);
        if (responseBody != null)
        {
            List<T> parsedObject = serverXmlParser.parseXmlToCustomObject<T>(responseBody);
            if (parsedObject != null)
            {
                successDelegate(parsedObject);
            }
        }
    }

And parsing method:

public List<T> parseXmlToCustomObject<T> (string xml)
        where T : ServerObject, new()
    {
        XDocument doc = XDocument.Parse(xml);
        List<T> result = 
            (
                from x in doc.Element("workflowschemeList").Elements("workflowscheme")
                select new T().Setup(x)
                ).ToList();
        return result;
    }

Where ServerObject is a baseobject to all my custom object classes containing .Setup. The error I now get is on line "List result = " with error:

Cannot implicitly convert type `System.Collections.Generic.List<xxxx.Core.Network.Objects.ServerObject>' to `System.Collections.Generic.List<T>'

Upvotes: 0

Views: 196

Answers (2)

Daniel Hilgarth
Daniel Hilgarth

Reputation: 174329

If all your classes have a public parameterless constructor, you can add the constraint where T : new() to the parseXmlToCustomObject. This will get rid of the first error.

To get rid of the second error, you will have to create an interface or base class that contains the Setup method and make all your classes implement this interface or derive from this base class and add that as another constraint: where T : ISetupable, new(). (Please forgive the name)

Your method would then look like this:

public List<T> parseXmlToCustomObject<T> (string xml, String root, String key)
    where T : ISetupable, new()
{
        XDocument doc = XDocument.Parse(xml);
        List<T> result = 
            (
                from x in doc.Element(root).Elements(key)
                select new T().Setup(x)
                ).ToList();
        return result;
}

If you are unable to ensure that all classes have a public parameterless constructor you will have to resort to factories to create your objects.

Upvotes: 4

polkduran
polkduran

Reputation: 2551

You need to specify a constraint to your generic method in order to make a new instance of T

public List<T> parseXmlToCustomObject<T> (string xml, String root, String key) where T : new(){
[...]
}

[Edit] for your second question: you need to have a base class or interface having the Setup() method and another constraint to your generic method:

Let's say you have a base class BaseClass having your Setup() method:

public List<T> parseXmlToCustomObject<T> (string xml, String root, String key) where T : BaseClass, new(){
[...]
}

Upvotes: 2

Related Questions