简体   繁体   中英

Abstract class and interface with the same generic method

I'm writing two APIs that I will use with many of my projects. Some projects my use one of the APIs, some the other, but the majority of my projects will use both. I'm trying to design them as if they're completely separate, but I'm struggling on one thing.

namespace FirstApi {
    public abstract class MyBaseClass {
        //constructor, some methods and properties

        public IEnumerable<T> Search<T>() where T : MyBaseClass, new() {
            //search logic here. must use generics as I create new instances of T here
        }
    }
}


namespace SecondApi {
    public interface IMyInterface {
        //some property and method signatures

        IEnumerable<T> Search<T>() where T : IMyInterface, new();
    }
}

namespace MyProject {
    public class MyDerivedClass : MyBaseClass, IMyInterface {

    }
}

Both APIs require this search method. The second API has some functionality in other classes that calls IMyInterface.Search<T>() , and I would like those classes that inherit MyBaseClass to use the Search<T> function defined in MyBaseClass .

Compilation error: The constraints for type parameter 'T' of method 'MyBaseClass.Search()' must match the constraints for type parameter 'T' of interface method 'IMyInterface.Search()'. Consider using an explicit interface implementation instead.

Note: When Search is called, T will always be the derived class of whichever abstract class or interface has been inherited. This was the only way I could find of achieving this in C# 2.0 ( C# abstract class return derived type enumerator ), and it's just caused more problems!

Is there a type-safe way that I can achieve this, without using objects and casting?

Solution:

Based on the accepted answer by Andras Zoltan, I created this class in my project, and will have to re-create this class for each project that uses both APIs.

public abstract class ApiAdapter<TAdapter> : MyBaseClass, IMyInterface where TAdapter: MyBaseClass, IJsonObject, new()
{
    IEnumerable<T> IJsonObject.Search<T>()
    {
        foreach (TAdapter row in base.Search<TAdapter>())
            yield return (T)(IMyInterface)row;
    }
}

I then inherit this class like so.

public class Client : ApiAdapter<Client> {
    //everything else can go here
}

You can explicitly implement the interfaces Search method, eg

    public class MyDerivedClass : BasicTestApp.FirstApi.MyBaseClass, BasicTestApp.SecondApi.IMyInterface
    {
        IEnumerable<T> SecondApi.IMyInterface.Search<T>()
        {
            // do implementation
        }
    }

However, I think you are asking for the MyBaseClass Search method to be called when the part of the code that handles your object as IMyInterface calls the Search<T> method. I cannot see a way because you have two T types with different constraints that cannot be related. If you did where T: BasicTestApp.FirstApi.MyBaseClass, IMyInterface, new(); in both definitions of the Search method then you would not have a problem but this would tie both your APIs together

Here is a possible implementation of your explicitly implemented interface method. It doesn't avoid the cast but at least keeps it neat.

        IEnumerable<T> SecondApi.IMyInterface.Search<T>()
        {
            var results = base.Search<MyDerivedClass>();

            return results.Cast<T>();
        }

I started my answer with exposition on why it's not working for you, but I think that's well understood now so I'll leave it out.

I've upvoted @IndigoDelta's answer but it highlights something I don't like about the overall design here - I have a sneaking suspicion you should actually be using a generic interface and generic class; not generic methods because it doesn't make any sense that:

Note : When Search is called, T will always be the derived class of whichever abstract class or interface has been inherited.

I'm throwing this solution into the mix; which I think is better because it means that each derived type doesn't need to reimplement the IMyInterface.Search method, and it goes some way to actually enforcing this rule you mention. It's a generic type dedicated to join the two APIs together, meaning the derived types don't need to do anything:

namespace MyProject
{
  using FirstApi;
  using SecondApi;

  public class SecondAPIAdapter<T2> : MyBaseClass, IMyInterface
    where T2 : SecondAPIAdapter<T2>, new()
  {
    #region IMyInterface Members
    IEnumerable<T> IMyInterface.Search<T>()
    {
      return Search<T2>().Cast<T>();
    }
    #endregion
  }

  //now you simply derive from the APIAdapter class - passing
  //in your derived type as the generic parameter.
  public class MyDerivedClass : SecondAPIAdapter<MyDerivedClass>
  { }
} 

i think you can do explicit implementation of interface and when you will access methor thru IMyInterface.Search - compiler will run the right method.

You need to use an explicit implementation.

public class MyDerivedClass : MyBaseClass, IMyInterface
{
    // The base class implementation of Search inherited

    IEnumerable<T> IMyInterface.Search<T>()
    {
        // The interface implementation
        throw new NotImplementedException();

        // this would not work because base does not implement IMyInterface 
        return base.Search<T>();
    }     
}

Since the implementations are different this makes sense. If they are not different then either the base class should implement the interface and you should use covariance (.Net 4.0 only) to combine your contraints or, perhaps you don't need the interface at all.

I hope I'm not confused, could you not change your definitions, such that:

public interface IMyInterface<in T>
{
    //some property and method signatures

    IEnumerable<U> Search<U>() where U : T, new();
}

Providing a generic argument of T which can use to enforce that the implementation provides a search function constraint to types of T :

public abstract class MyBaseClass : IMyInterface<MyBaseClass>
{
    public virtual IEnumerable<T> Search<T>() where T : MyBaseClass, new()
    {

    }
}

That way, your derived types are simply:

public class MyDerivedClass : MyBaseClass
{

}

Which you can then do searches as:

var derived = new MyDerivedClass();
IMyInterface<MyDerivedClass> iface = impl;

var results = iface.Search<MyDerivedClass>();

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