Qwerty01
Qwerty01

Reputation: 779

Use child type in parent/interface

is there a way to use the child type in an interface? I am working on making a serialization interface:

interface ISerialize
{
    byte[] Serialize();
    ? Deserialize(byte[] serialized); // what would go here?
}

When it serializes, the class should return a byte array representing the object, and when it deserializes, it should return a new class of itself based on the serialized data. Is there any way to use the child type in the interface? I would rather not use ISerialize as the return type so that casting to the child class won't be necessary. I also believe generics shouldn't be necessary because the implemented class will only return it's own type.

Note: I am not looking for alternative serialization methods (although you're welcome to comment with recommendations), I'm curious about this question in general.

Edit: I know deserialize should be void, but I couldn't think of another case off the top of my head where this question would be applicable.


What I did to solve the serializing problem is with the following code:

interface ISerialize
{
    byte[] Serialize();
    void Deserialize(byte[] serialized);
}
public static class ISerializeExtensions
{
    public static T Deserialize<T>(this T sender, byte[] serialized) where T : ISerialize
    {
        T ret = default(T);
        ret.Deserialize(serialized);
        return ret;
    }
}

This overcame the issue of needing to create a new object to deserialize. I am still interested in the original question though.

Upvotes: 1

Views: 333

Answers (1)

Kyle
Kyle

Reputation: 6684

Here's why you can't do this without generics. Say I have a collection that looks like this:

List<ISerialize> serialzeObjects = new List<ISerialize>();

If there were a way to do what you want, what would you put here:

foreach( var obj in serializeObjects )
{
    ? deserialized = obj.Deserialize( /* ... */ );
}

There's no way for the compiler to verify the return type of Deserialize if it could be anything that implements ISerialize. The best the compiler could do is say that the object it gets back implements ISerialize. That's what you get if you declare your interface this way:

public interface ISerialize
{
    byte[] Serialize();
    ISerialize Deserialize( byte[] data );
}

You have to cast to the implementing type because there's no way in general to know at compile time what the return type really is.

An alternative is to use generics:

public interface ISerialize<T>
{
    byte[] Serialize();
    T Deserialize( byte[] data );
}

Now you're able to have a specific return type. But you do lose something here. ISerialize<T> and ISerialize<U> can't be stored in the same location, because the interface is invariant in its type parameter. But we can do something about this:

public interface ISerialize<out T>
{
    byte[] Serialize();
    T Deserialize( byte[] data );
}

Now we've made the ISerialize<T> interface covariant in T. That means we can do something like this:

ISerialize<object> serializable = new Foo();

Provided that Foo looks something like this:

public class Foo : ISerialize<Foo>
{
    /* ... */
}

So now, in a sense, the ISerialize<T> interface respects the inheritance hierarchy for its type parameter.

EDIT: If you want to add the constraint that a class may only implement ISerialize on itself, that is perfectly possible to do so:

public interface ISerialize<out T> where T : ISerialize<T>
{
    byte[] Serialize();
    T Deserialize( byte[] data );
}

This will now prevent you from doing something like:

public class Dog : ISerialize<Cat>
{
}

However, as pointed out in the comments if Cat : ISerialize<Cat>, then this constraint won't help you.

Upvotes: 4

Related Questions