Search code examples
c#methodsreflectionextension-methods

C# Generic Return Type of Calling Object Type


I am making a class to abstract some functionality that I will be using in multiple other classes in my project. This class converts objects to and from JSON and also deep copies objects.

public abstract class Serializable
{
    public T Copy<T>()
    {
        return Deserialize<T>(Serialize());
    }

    public string Serialize()
    {
        return JsonConvert.SerializeObject(this);
    }

    public static T Deserialize<T>(string json)
    {
        return JsonConvert.DeserializeObject<T>(json);
    }
}

This code works as intended; however, I would like to simplify the syntax of the Copy and Desirialize functions. Currently, a call looks like this copy = descendantClassInstance.Copy<descendantClass>() which is wordy and also leaves room for error in that copy = otherdescendantClassInstance.Copy<descendantClass>() would compile but yield an error at runtime. I would prefer the return type be automatically inferred based on the type of the calling object, like copy = descendantClassInstance.Copy(), without overriding the Copy or Desirialize method in each descendant class.

How can I do this?

Edit:

Extensions seem to be a possible avenue for implementing Copy as desired. However, I also want similar functionality for Deserialize, which is static. From here, it doesn't seem doable with extensions.

An acceptable answer will handle both of these case.


Solution

  • One option is to use

    public abstract class Serializable<T>
    {
        public T Copy()
        {
            return Deserialize(Serialize());
        }
    
        public string Serialize()
        {
            return JsonConvert.SerializeObject(this);
        }
    
        public static T Deserialize(string json)
        {
            return JsonConvert.DeserializeObject<T>(json);
        }
    }
    

    And to implement it like this:

    public class Test : Serializable<Test>
    {
    }
    

    However, note that this will require you to continue the chain if you want further descendants of your class to have the most derived type:

    public class Base<T> : Serializable<T> where T : Base<T>
    {
    }
    
    public class Derived : Base<Derived>
    {
    }
    

    This may or may not be acceptable depending on your use case.