Jonas Lima
Jonas Lima

Reputation: 21

Can a class property be both T or IEnumerable<T>?

Is there a way for a property in a class to accept both T or IEnumerable<T> as it's value?

I'm trying to implement a DTO for an API Success Response, where the property Data can both be a single JSON Object or a collection of JSON Objects. Is there a way to use one single class to achieve this or do I need to have two classes, one for T and other for IEnumerable<T>?

Here's my code:

public class ApiClass<T> where T : class, IEnumerable<T>
{
    public ApiClass(IEnumerable<T> data)
    {
        Data = data;
        Message = "Enumerable data received";
    }

    public ApiClass(T data)
    {
        Data = data;
        Message = "Object data received";
    }

    private IEnumerable<T> Data { get; set; }

    private string Message{ get; set; }
}

And that's the result I intend to have, after serializing an instance of the object:

{
  "data": {
    "Id": 1,
    "Name": "John"
  },
  "message": "Object data received."
}

or

{
  "data": [
    {
      "Id": 1,
      "Name": "John"
    },
    {
      "Id": 2,
      "Name": "Mary"
    }
  ],
  "message": "Object data received."
}

This code compiles, however I get the following error message when I try to create an new object as in return new ApiClass<User>(new User());:

Error: CS0311 - The type 'XPTO.User' cannot be used as type parameter 'T' in the generic type or method 'ApiClass<T>'. There is no implicit reference conversion from 'XPTO.User' to 'System.Collections.Generic.IEnumerable<XPTO.User>'.

Upvotes: 1

Views: 125

Answers (2)

Chris Pratt
Chris Pratt

Reputation: 239430

Use multiple classes and let the programmer decide which to inherit from based on the return they need. For example:

public class ApiClass
{
    public string Message{ get; private set; }
}

public class ApiClass<T> : ApiClass where T : class
{
    public ApiClass(T data)
    {
        Data = data;
        Message = "Object data received";
    }

    public T Data { get; private set; }
}

public class ArrayApiClass<T> : ApiClass where T : class
{
    public ArrayApiClass(IEnumerable<T> data)
    {
        Data = data;
        Message = "Enumerable data received";
    }

    public IEnumerable<T> Data { get; private set; }
}

Upvotes: 3

Ryanman
Ryanman

Reputation: 880

I don't believe there's a way to do this. As someone who would potentially consume an API, you do NOT want people to guess if their data is an array or not!

Instead, have a property called "MainData" or something if there's no collection. Otherwise people would much rather just get the first element in an array if there's a contract to always have a collection.

Upvotes: 2

Related Questions