简体   繁体   中英

Can a Base Class Method return the type of the derived class?

Based on the other posts I have read, it seems that this may not be possible, but I thought I would post what I am trying to do and see if anyone knows of a solution.

I am trying to add a "Clone()" method to classes generated from a Telerik Open Access domain model. No problem. I was able to figure out how to add a base class to the generated entity models so that I could identify those classes by their base class. ( All entities inherit from a base class )

I want ALL these entity model classes to be able to Clone themselves. I have found a solution for that as well. ( Deep Cloning Objects )

Now I have a base class and I want to add a Clone() function to every class that derives from that base class. So ... it seems that the base class is the natural place to put it ... right?

public abstract class DBEntityBase
{
    /// <summary>
    ///     Gets a COPY of the instance in it's current state
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    protected T Clone<T>()
    {
        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(this));
    }
}

I add the protected generic Clone() method because at the base class level we don't know the Type we are cloning. The Clone() method needs to be implemented by the entity model itself in order to provide the specific type being cloned.

public partial class DeliverableEntity
{
    public new DeliverableEntity Clone()
    {
        return this.Clone<DeliverableEntity>();
    }
}

This works fine, but does not guarantee that derived classes will publicly expose a Clone() method, so I added an abstract Clone() method to the base which will require the derived class to implement a public Clone() method.

public abstract class DarkRoomDBEntityBase
{
    /// <summary>
    ///     Gets a COPY of the instance in it's current state
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    protected T Clone<T>()
    {
        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(this));
    }

    /// <summary>
    ///     Gets a deep COPY of the entity instance
    /// </summary>
    /// <returns></returns>
    public abstract DBEntityBase Clone();
}

I give it a return type of the base class itself; every implementation MUST return a value which adheres to the DBEntityBase, which of course, all derived classes do. Since the Clone() method is returning a type of the derived class itself then ... it seems to make sense that this would work.

DeliverableEntity originalEntity = new DeliverableEntity();
DeliverableEntity clonedEntity   = originalEntity.Clone();

When building, however, I get the error ..

'DeliverableEntity' does not implement inherited abstract member 'DBEntityBase.Clone()'

Presumably due to the return type.

I know that I COULD just put the Clone() method in a separate utility class and not implement it directly in each entity model ... that would get me around my problem (and probably save a lot of implementation code), but I am still left wondering why this won't work. It seems like there should be a way to do this.

UPDATE

In response to @Luann's first reply (thank you) I made the change to "override" ...

public partial class DeliverableEntity
{
    public override DeliverableEntity Clone()
    {
        return this.Clone<DeliverableEntity>();
    }
}

and am now receiving the following error ...

return type must be 'DBEntityBase' to match overridden member 'DBEntityBase.Clone()'

SOLUTION

Thanks to Flynn1179 I was able to move forward again. I thought I would take a moment to document what I did here for future reference ..

Instead of creating a partial class for each entity model in the ORM, implementing an abstract method, I created a single extension method as suggested.

namespace DAL
{
    public partial class DeliverableEntity : DBEntityBase
    {
         // ... Code generated from ORM 
    }

    public partial class DeliverableItemEntity : DBEntityBase
    {
         // ... Code generated from ORM 
    }

    public partial class DeliverableItemAttrEntity : DBEntityBase
    {
         // ... Code generated from ORM 
    }
}

namespace DAL
{
    public static class EntityExtensionMethods
    {
        public static T Clone<T>(this T entity) where T: DBEntityBase
        {
            return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(entity));
        }
    }
}

Some things to note ...

  • Put this class in the same Namespace as the entity models. If it's in a different namespace then you will need to add that namespace in order to have access to the method.
  • I defined the constraint on the generic type to only and all classes which inherit the DBEntityBase class. Since I made all my entity model classes derive from this base class I know that all of them will will expose this method but also that any class which does NOT derive from this class will NOT have this capability.
  • The extension method and the class that contains it must be static

Now the cool part is that ALL the Entities have access to the function ...

    // Create the original instances of the entities    
    DeliverableEntity origDeliverable       = new DeliverableEntity();
    DeliverableItemEntity origItem          = new DeliverableItemEntity();
    DeliverableItemAttrEntity origItemAttr  = new DeliverableItemAttrEntity();

    // now here's the magic 

    DeliverableEntity cloneDeliverable      = origDeliverable.Clone();
    DeliverableItemEntity cloneItem         = origItem.Clone();
    DeliverableItemAttrEntity cloneItemAttr = origItemAttr.Clone();

I love this solution as it's has the simplicity of a base class where the implementation is defined in a single location (whereas I was looking at implementing an abstract method individually in each derived class) plus since it's associated with the DBEntityBase class and in the same namespace, it becomes part of the "contract' defined by the base class which means I can count on it being available whenever I have a class derived from DBEntityBase.

By popular request..

Try an extension method:

public T Clone<T>(this T obj) where T : DBEntityBase
{
  return /* insert code that creates clone here */
}

I have to be honest, I didn't think this would work, as I expected C# would be unable to determine exactly what it's an extension of. Apparently however, it does!

The new C# version, 9.0, will support covariant returns , which means you can override Clone() and return a more specific type. This will compile:

public partial class DeliverableEntity
{
    public override DeliverableEntity Clone()
    {
        return this.Clone<DeliverableEntity>();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM