JDBennett
JDBennett

Reputation: 1505

Generic List as Class Property

I am trying to use generics on a List property for a class.

Basically I am using a message based service that would receive a collection of Message Requests. For each Message Request received, I would return a corresponding Message Response.

So my implementation would look something like this:

public class MessageRequest
{
    private string _messageId;
    private string _serviceMethod;

    public MessageRequest(string id, string operation)
    {
        _messageId = MessageId;
        _serviceMethod = operation;
    }

    public string MessageId { get { return _messageId; } }
    public string ServiceMethod { get { return _serviceMethod;  } }
}

public class MessageResponse
{
    private List<T> _data; <--This does't Work..
    private string _messageId;

    public string MessageId { get { return _messageId; }}
    public List<T> Data { get { return _data; }}
}

public List<MessageResponse> GetData(List<MessageRequest> requests)
{
  List<MesssageResponse> responses = new List<MessageResponse>();
  foreach(MessageRequest r in requests)
  {
     //I will determine the collection type for the response at runtime based
     //on the MessageRequest "ServiceMethod"
     List<TypeIFiguredOutFromServiceMethod> data = getData();

     responses.add(new MessageResponse() 
         { 
            MessageId = r.MessageId,
            Data<TypeIFiguredOutFromServiceMethod> = data
         });

Something like that...

I can't specify the List Type on the MessageResponse class that is this:

public class MessageResponse<T>
{
}

because the collection of MessageRequests will have different operations and thus will require different collection results.

Upvotes: 1

Views: 5515

Answers (2)

JDBennett
JDBennett

Reputation: 1505

As it would turn out this topic has been talked about a few times on SO. I am going to post what I did so hopefully someone can benefit from this (or even someone gives me a better way of accomplishing this).

The intent of my implementation was to pass into a Service Manager object a collection of request objects; with each request object specifying an Operation and any additional parameters required for that operation.

My service implementation would then fetch a response for each request object received - the response data would vary in type - the determinant being the operation specified in the request. That is if I have an operation that is "GetCatalog", the result from that request would be a List<Items>. Conversely a "GetAddressbooks" would yield List<AddressbookRecords>.

This is where I needed a generic property on a class. My Message response object would have a generic List as a property.

In the end I ended up using a combination of @Mihai Caracostea suggestion to use object and the solution posted here.

First I modified the MessageRequest and MessageResponse objects both for clarity and efficiency:

    public class MessageRequest
{
    private readonly string _messageId;
    private readonly Operation _operation;

    public MessageRequest(string id, Operation operation)
    {
        _messageId = id;
        _operation = operation;
    }

    public string MessageId { get { return _messageId; } }
    public Operation Operation { get { return _operation;  } }
}

    public class MessageResponse
{
    private object _data;
    public MessageRequest Request { get; set; }

    public T Data<T>()
    {
        return (T)Convert.ChangeType(_data, typeof(T));
    }

    public void SetData(object data)
    {
        _data = data;
    }
}

The MessageResponse definition really enables this. Using the getter / setter approach to the property - I use the Object _data field to set the data received from a backing service and the T Data to basically cast the data to what it should be when the client receiving the MessageResponse object reads the data.

So the Service Manager Implementation looks like this:

public List<MessageResponse> GetData(List<MessageRequest> messageRequests)
    {
        List<MessageResponse> responses = new List<MessageResponse>();
        try
        {
            foreach (MessageRequest request in messageRequests)
            {
                //Set up the proxy for the right endpoint
                SetEndpoint(request);

                //instantiate a new Integration Request with the right proxy and program settings
                _ir = new IntegrationRequest(_proxy, ConfigureSettings(request));

                MessageResponse mr = new MessageResponse { Request = request };

                using (IntegrationManager im = new IntegrationManager(_ir))
                {
                    mr.SetData(GetData(im, request));
                }

                responses.Add(mr);
            }

            return responses;
        }//
        catch (Exception)
        {

            throw;
        }

The client implementation consuming the result of the GetData method looks like:

List<MessageRequest> requests = new List<MessageRequest>();
        requests.Add(new MessageRequest(Guid.NewGuid().ToString(), Operation.GetBudgets));
        requests.Add(new MessageRequest(Guid.NewGuid().ToString(), Operation.GetCatalogItems));
        List<MessageResponse> responses;
        using (ServiceManager sm = new ServiceManager())
        {
            responses = sm.GetData(requests);
        }

        if (responses != null)
        {

            foreach (var response in responses)
            {
                switch (response.Request.Operation)
                {
                    case Operation.GetBudgets:
                        List<Budget> budgets = response.Data<List<Budget>>();
                        break;
                    case Operation.GetCatalogItems:
                        List<Item> items = response.Data<List<Item>>();
                        break;

                }
            }
        }

This is just a test - but basically I constructed two MessageRequest objects (get budgets, and get catalog items) - posted to the Service and a collection of the MessageResponse objects returned.

This works for what I need it to do.

Two additional points I want to mention on this subject are one I looked a using reflection to to determine the response types at runtime. The way I was able to to do it was by specifying a custom attribute type on the operation enum:

 public enum Operation
{
    [DA.Services.ResponseType (Type = ResponseType.CreateOrder)]
    CreateOrder,

    [DA.Services.ResponseType(Type = ResponseType.GetAddressbooks)]
    GetAddressbooks,

    [DA.Services.ResponseType(Type = ResponseType.GetCatalogItems)]
    GetCatalogItems,

    [DA.Services.ResponseType(Type = ResponseType.GetAddressbookAssociations)]
    GetAddressbookAssociations,

    [DA.Services.ResponseType(Type = ResponseType.GetBudgets)]
    GetBudgets,

    [DA.Services.ResponseType(Type = ResponseType.GetUDCTable)]
    GetUDCTable
}

class ResponseType : System.Attribute
{
    public string Type { get; set; }

    public const string CreateOrder = "Models.Order";
    public const string GetAddressbooks = "Models.AddressbookRecord";
    public const string GetCatalogItems = "Models.Item";
    public const string GetAddressbookAssociations = "Models.AddressbookAssociation";
    public const string GetBudgets = "Models.Budget";
    public const string GetUDCTable = "Models.UdcTable";
}

I basically looked at using Activator.CreateType() to dynamically create the response object for the client by evaluating the ResponseType.Type on the operation specified in the request.

While this was elegant - I found it was not worth the time expense to process. This implementation has fairly well defined objects that haven't changed in years. I am willing to write a switch statement to cover all scenarios rather than using reflection for the flexibility. The reality is I just don't need the flexibility in this particular instance.

The second point I want to mention (just for anyone that reads this) edification is "Why" a generic cannot be used as a class property. As it would turn out this was also debated. There were arguments that went from "it doesn't make sense" to "microsoft felt it was too hard to do in the release and abandoned it". Those discussions can be found here and here.

In the end one of those threads provided a link to a technical reason. That reason being the compiler would have no way of determining how much memory to allocate for an object that has a generic property. The author of the article was Julian Bucknail and can be found here.

Thank you to everyone that posted suggestions in finding my solution.

Upvotes: 0

Enigmativity
Enigmativity

Reputation: 117009

Since you are dealing with messages that most likely come in as strings that you need to parse anyway, I would be inclined to keep them as strings like this:

public class MessageResponse
{
    public string MessageId { get; private set; }
    public Type MessageType { get; private set; }
    public List<string> Data { get; private set; }
}

If your code has already performed the parsing then change string to object and go with that.

Upvotes: 2

Related Questions