Search code examples
c#genericsboxingunboxing

Using generic in the interface


Update:

Heinzi is right. AutoCAD Polyline is reference type and not a struct. Good point. But I have simplified the scenario as what I am dealing with in the real application is an AutoCAD object which is struct. So please consider both as struct not reference type.


I am looking for the right approach to take in this situation and would appreciate if anyone can shed light or help me to better understanding.

There is an interface in the data access layer with two implementations to deal with two different providers: AutoCad and Sketchup APIs.

interface IEntity
{
    void object GetPoly();
    void void   InsertPoly(object poly);
}

class AutocadEntity
{
    void object GetPoly()
    {
         //calling Autocad APIs
         return Autocad Polyline object
    }
    void InsertPoly(object poly){...}
}

Autocad implementation of GetPoly would return Polyline object as this is what defined in Autocad APIs as polyline, whereas Sketchup would return Face object.

I have defined the return type(and the parameter) as object to deal with these different types. The cost is performance issue where boxing/unboxing happens. And it shows itself more boldly where the return/parameter is object[].

I first wondered making the method return/parameter type generic is the solution but then I think it wouldn't be as the implementations are type specific.


Solution

  • Try using the adapter pattern to adapt the PolyLine and Face types to a single type that you would prefer to work with. For example:

    public abstract class BasePoly
    {
        public abstract double X { get; set; }
        public abstract double Y { get; set; }
        public abstract double Width { get; set; }
        public abstract double Height { get; set; }
    }
    public abstract class BasePoly<T> : BasePoly
    {
        public T poly { get; private set; }
        protected BasePoly(T poly) { this.poly = poly; }
    }
    
    public class PolyLineAdapter : BasePoly<PolyLine>
    {
        public PolyLineAdapter(PolyLine poly) : base(poly) {}
        // override abstracts and forward to inner PolyLine instance at 'this.poly'
    
        public override double X { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
    
        public override double Y { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
    
        public override double Width { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
    
        public override double Height { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
    
    }
    
    public class FaceAdapter : BasePoly<Face>
    {
        public FaceAdapter(Face poly) : base(poly) {}
        // override abstracts and forward to inner Face instance at 'this.poly'
    
        public override double X { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
    
        public override double Y { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
    
        public override double Width { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
    
        public override double Height { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
    
    
    }
    
    interface IEntity
    {
        BasePoly GetPoly();
        void   InsertPoly(BasePoly poly);
    }
    
    public abstract class Entity<TEntity> : IEntity
        where TEntity : BasePoly
    {
        public BasePoly GetPoly()
        {
            return this.GetExternalPoly();
        }
        public abstract TEntity GetExternalPoly();
        public void InsertPoly(BasePoly poly)
        {
            this.InsertExternalPoly((TEntity) poly);
        }
        public abstract void InsertExternalPoly(TEntity poly);
    }
    
    public class AutocadEntity : Entity<PolyLineAdapter>
    {
        public override PolyLineAdapter GetExternalPoly()
        {
            throw new NotImplementedException();
        }
        public override void InsertExternalPoly(PolyLineAdapter poly)
        {
            throw new NotImplementedException();
        }
    }
    
    public class SketchupEntity : Entity<FaceAdapter>
    {
        public override FaceAdapter GetExternalPoly()
        {
            throw new NotImplementedException();
        }
        public override void InsertExternalPoly(FaceAdapter poly)
        {
            throw new NotImplementedException();
        }
    }
    
    // fills for third party classes
    public class PolyLine {}
    public class Face {}
    

    Using the adapter pattern, you provide a proxying layer to conform the two third party types to a type that you want to work with.

    Keep in mind that this design assumes that you will only be working with one type of third party engine at a time. If you will be working with both engines at the same time, then make the following change:

    public class BasePoly
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double Width { get; set; }
        public double Height { get; set; }
    }
    
    interface IEntity
    {
        BasePoly GetPoly();
        void InsertPoly(BasePoly poly);
    }
    
    public abstract class Entity : IEntity
    {
        public abstract BasePoly GetPoly();
        public abstract void InsertPoly(BasePoly poly);
    }
    
    public class AutocadEntity : Entity
    {
        public override BasePoly GetPoly()
        {
            // retrieve external type, convert it to BasePoly and return that
            throw new NotImplementedException();
        }
        public override void InsertPoly(BasePoly poly)
        {
            // convert BasePoly to external type and insert that
            throw new NotImplementedException();
        }
    }
    
    public class SketchupEntity : Entity
    {
        public override BasePoly GetPoly()
        {
            // retrieve external type, convert it to BasePoly and return that
            throw new NotImplementedException();
        }
        public override void InsertPoly(BasePoly poly)
        {
            // convert BasePoly to external type and insert that
            throw new NotImplementedException();
        }
    
    }
    
    // fills for third party classes
    public class PolyLine {}
    public class Face {}
    

    Also, if you are concerned about the cost of the adapter boxing or conversion operations (which I wouldn't be until you've actually measured and determined whether or not it needs to be optimized), then you can apply the adapter pattern to the callers that are consuming IEntity instead of the PolyLineAdapter/FaceAdapter or AutocadEntity/SketchupEntity types themselves. Essentially, build a plugin engine. You may be able to use Generics to abstract the common idioms between the two implementations.

    Here is a dotnetfiddle example: https://dotnetfiddle.net/UsFPM7