1

I'm somewhat new to generics in C#. I have a IResult interface which has the following properties:

    public interface IResult<T>
    {
         string ResultMessage { get; set; }
         T Data { get; set; }
    }

The purpose being that inheriting objects will be returning a string message with a payload to the UI. This will be delivered via another object which has detailed information and handles serialize/deserialization, which will contain an IEnumerable of these objects. However, it needs to hold any IResult, as there may be multiple types in the one object. It must also only hold IResults in that collection. This is the property which is intended to contain them:

public IEnumerable<IResult<dynamic>> Results { get; set; }

The problem is, casting with IEnumerable.Cast() or an explicit inline cast doesn't work. The presence of dynamic here was just an attempt by me after doing the same thing with object and the cast failing then as well.

How can I have my larger payload object accept a collection of IResult<T>, since none of these work (the first because T is undefined in the context?)

IEnumerable<IResult<T>> Results { get; set; } 
//doesn't work as T is undefined, and if defined at the payload level, would result in a single type
IEnumerable<IResult<dynamic>> Results { get; set; } 
//doesn't work, the cast fails
IEnumerable<IResult<object>> Results { get; set; } 
//doesnt work, the cast fails
C. Allen
  • 35
  • 4
  • Does this answer your question? [C# variance problem: Assigning List as List](https://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase) The problem you are dealing with can sometimes be solved with covariant interfaces, but you cannot have a setter – Charlieface May 13 '21 at 21:55

3 Answers3

1

What you want to achieve is not related to generics in any case. It is important to understand that by default generics are invariant, e.g.:

IInterface<One> != IInterface<Two>

even if Two: One.

You can read more about it here

One of the simplest ways to achieve what you want is to use objects

public interface IResult
{
     string ResultMessage { get; set; }
     object Data { get; set; }
}

If, however, you want to stick to generics, you can define your IResult interface as covariant:

public interface IResult<out T>
{
    string ResultMessage { get; set; }
    T Data { get; } // note that you cannot specify setter in this case
}

Then, define your Result classes like this:

public class ObjectResult: IResult<object>
{
    private readonly object _data;

    public ObjectResult(object data)
    {
        _data = data;
    }

    public string ResultMessage { get; set; }
    public object Data => _data;
}

public class StringResult : IResult<string>
{
    private readonly string _data;

    public StringResult(string data)
    {
        _data = data;
    }

    public string ResultMessage { get; set; }
    public string Data => _data;
}

Now you're able to go:

IResult<string> stringResult = new StringResult("string");
IResult<object> objectResult = new ObjectResult(new object());

objectResult = stringResult;

Console.WriteLine(objectResult.Data); // prints "string"
Gleb
  • 1,723
  • 1
  • 11
  • 24
0

Honestly, I don't think you can do it this way. The only way I can think of is to have a non-generic version of the interface that IResult implements. If you need the data property, then you might be able to set it up like this:

public interface IResult
{
    string ResultMessage { get; set; }
    object DataObject { get; }  
}

public interface IResult<T> : IResult
{
    T Data { get; set; }          
}


public class SomeResult<T> : IResult<T>
{
    public string ResultMessage { get; set; }
    public T Data { get; set; }
    public object DataObject => Data == null ? null : (object)Data;
}

Then you would have an IEnumerable that you'd be able to loop through and get the data for. You might even be able to do some trick where each implementation formats it a certain way, but this is how I'd approach it.

Also, you could use dynamic instead of object here, but I'm not entirely sure what your end goal is.

Daniel Lorenz
  • 4,178
  • 1
  • 32
  • 39
0

Trying to provide constructive criticism here: why are you trying to force the two things together into one interface? You can't do multiple inheritance with classes in C#, but you can do it with interfaces.

In reading your question, you state:

The purpose being that inheriting objects will be returning a string message with a payload to the UI.

So, that's one function, and then

This will be delivered via another object which has detailed information and handles serialize/deserialization

which sounds like a totally different function. I would split up the interfaces, like

public interface IGiveResults
{
    string Results{get; }
}

and

public interface IGiveData<T>
{
    T Data{get;}
}

This isn't really going to fix your problem, though, so I'll give you classic Stack Exchange answer, "Don't do A, do B. Nobody does A."

If your goal is serialization, don't try to force your things in the IEnumerable to return all kinds of different types and to then try to awkwardly wrangle that list.

Instead, consider adding the serialize and deserialize functions to the interface, and have the objects accept the serializer. Consider:

public interface ISerializable
{
    void Serialize(Serializer serializer);
    void Deserialize(Deserializer deserializer);
}

You could even include that with your results interface:

public interface IGiveResults : ISerializable
{
    string Results{get;}
}

NOW what you can do is, when you get to the serialize or deserialize step, you can have your serializer pass itself off:

public class Serializer
{
    public void SerializeAll(IEnumerable<IGiveResults> results)
    {
        foreach(var result in results)
        {
            result.Serialize(this);
        }
    }
    public void Serialize<T>(T Data)
    {
        <do your thing>
    }
}

This flips your question around, but I think better accomplishes what you want to do. NOW your subclasses that are inheriting from the ISerializable interface can just do:

public class YourIntSubclass : IGiveResults
{
    public string Results{get; private set;} = "passed!";
    private int[] Data = \\whatever
    public void Serialize(Serializer serializer)
    {
        serializer.Serialize(Data);
    }
}

You just implement the type-specific serialization in your serializer class and you're good to go. You can do type checking on serialize with pattern matching and throw yourself a handy exception if you've forgotten to cover a case, like

public void Serialize<T>(T data)
{
    switch(data)
    {
        case int intData:
            <serialize an int>
            break;
        case float floatData:
            <serialize a float>
            break;
        default:
            throw new System.NotImplementedException("Forgot to implement a serializer for " + data.GetType().ToString());
    }
}
Chuck
  • 1,977
  • 1
  • 17
  • 23