Pregunta

I met some interesting covariance problem in my c# code.

I have a generic Matrix<T> class, and it's been instantiated for example Matrix<int>, Matrix<object> and Matrix<Apple>.

For my business logic, I've wrapped them into a generic Wrapper<T>. This Wrapper implements non-generic INonGenericWrapper interface. So, I have Wrapper<int>, Wrapper<object> and Wrapper<Apple>.

My problem is: I would like to define a container for all those 3 Wrappers. I can't say List<Wrapper<object>>, because I can't insert Wrapper<int> into this collection. I can't even say List<INonGenericWrapper>, because inside my foreach, I would like to access to the generic Matrix<T> parameter.

Cheesy part: this Wrappers will be (de-)serialized with the definite type: MySerializer<Wrapper<Apple>>.Serialize(_myInstanceOfWrappedApple).

I think it's clear that I would like to avoid huge switches of typeof when serializing..

What is my possible workarounds? I'm kinda stuck.

Thanks in advance,

¿Fue útil?

Solución

Recently I've come across such limitation and I implemented a variation of Visitor Pattern (may be abuse of it). But that helped me to solve the problem.

I'm no architect, just a developer. So pardon me if am abusing the design pattern, but sure this will help.

With provided information I created mocks of your classes and applied visitor pattern as follows.

public class Matrix<T>
{
    public T Obj { get; set; }
}

public interface INonGenericWrapper
{
    void Wrap();
    void Accept(IVisitor visitor);
}

public class Wrapper<T> : INonGenericWrapper
{
    public Matrix<T> Matrix { get; private set; }

    public void Wrap()
    {
        //Your domain specific method
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public interface IVisitor
{
    void Visit<T>(T element);
}

public class SerializationVisitor : IVisitor
{
    public void Visit<T>(T element)
    {
        new Serializer<T>().Serialize(element);
    }
}

public class Serializer<T>
{
    public Stream Serialize(T objectToSerialize)
    {
        Console.WriteLine("Serializing {0}", objectToSerialize);
        //Your serialization logic here
        return null;
    }
}

How to use:

List<INonGenericWrapper> wrappers = new List<INonGenericWrapper>();
wrappers.Add(new Wrapper<object>());
wrappers.Add(new Wrapper<string>());
wrappers.Add(new Wrapper<int>());
var visitor = new SerializationVisitor();//Create the operation you need to apply
foreach (var wrapper in wrappers)
{
    wrapper.Accept(visitor);
}

All you've to do is create a new visitor for each operation you need to perform.

Here is the Demo which outputs

Serializing Wrapper`1[System.Object]
Serializing Wrapper`1[System.String]
Serializing Wrapper`1[System.Int32]

Otros consejos

You have said that you want to be able to insert data into the collection based on the covariantly exposed type. This is simply not possible. For a type to be covariant it needs to not expose any means of inserting data. The type would need to be contravariant to support that, but if the type is contravariant then it cannot expose the information in a covariant manor.

In short, this is impossible, and not just impossible because of what C# as a language has implemented, but impossible on a conceptual level. It's not possible to implement a statically typed solution to this problem in any conceivable language.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top