Абстрактный класс и интерфейс с тем же универсальным методом - PullRequest
3 голосов
/ 25 мая 2011

Я пишу два API, которые я буду использовать во многих своих проектах. В некоторых проектах я использую один из API, в другом - другой, но большинство моих проектов будут использовать оба. Я пытаюсь создать их так, будто они полностью отделены друг от друга, но я борюсь за одну вещь.

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 {

    }
}

Оба API требуют этот метод поиска. Второй API имеет некоторые функции в других классах, которые вызывают IMyInterface.Search<T>(), и я хотел бы, чтобы те классы, которые наследуют MyBaseClass, использовали функцию Search<T>, определенную в MyBaseClass.

Ошибка компиляции: Ограничения для параметра типа 'T' метода 'MyBaseClass.Search ()' должны соответствовать ограничениям для параметра типа 'T' метода интерфейса 'IMyInterface.Search ( ). Попробуйте вместо этого использовать явную реализацию интерфейса.

Примечание: Когда вызывается Search, T всегда будет производным классом любого абстрактного класса или интерфейса, унаследованного. Это был единственный способ добиться этого в C # 2.0 ( C # абстрактный класс возвращает перечислитель производного типа ), и это только вызвало больше проблем!

Есть ли безопасный для типов способ, которым я могу достичь этого без использования объектов и приведения?

Решение:

Основываясь на принятом ответе Андраса Золтана, я создал этот класс в своем проекте, и мне придется заново создавать этот класс для каждого проекта, использующего оба API.

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;
    }
}

Затем я наследую этот класс следующим образом.

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

Ответы [ 5 ]

4 голосов
/ 25 мая 2011

Вы можете явно реализовать метод поиска интерфейсов, например,

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

Однако, я думаю, вы запрашиваете метод поиска MyBaseClass, который вызывается, когда часть кода, которая обрабатывает ваш объект, как IMyInterface, вызывает метод Search<T>. Я не вижу пути, потому что у вас есть два T типа с разными ограничениями, которые не могут быть связаны. Если вы сделали where T : BasicTestApp.FirstApi.MyBaseClass, IMyInterface, new(); в обоих определениях метода поиска, у вас не возникло бы проблемы, но это связало бы оба ваших API

Вот возможная реализация вашего явно реализованного метода интерфейса. Он не избегает актерского состава, но, по крайней мере, сохраняет его аккуратным.

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

            return results.Cast<T>();
        }
2 голосов
/ 25 мая 2011

Я начал свой ответ с рассказа о том, почему он не работает для вас, но я думаю, что теперь это хорошо понято, поэтому я его опущу.

Я проголосовал за ответ @ IndigoDelta, но он выделяет кое-что, что ямне не нравится общий дизайн здесь - у меня есть подозрение, что вы должны использовать универсальный интерфейс и универсальный класс;не универсальные методы, потому что не имеет никакого смысла, что:

Примечание : Когда вызывается Search, T всегда будет производным классом любого абстрактного класса или интерфейса, который былпо наследству.

Я добавляю этот раствор в смесь;что, я думаю, лучше, потому что это означает, что каждому производному типу не нужно переопределять метод IMyInterface.Search, и это дает некоторый способ фактически обеспечить соблюдение этого правила, о котором вы упоминаете.Это универсальный тип, предназначенный для объединения двух API, что означает, что производные типы не должны ничего делать:

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>
  { }
} 
1 голос
/ 25 мая 2011

Вам нужно использовать явную реализацию.

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>();
    }     
}

Поскольку реализации различны, это имеет смысл. Если они не отличаются, либо базовый класс должен реализовывать интерфейс, и вы должны использовать ковариацию (только .Net 4.0) для объединения своих ограничений или, возможно, вам вообще не нужен интерфейс.

1 голос
/ 25 мая 2011

Я думаю, что вы можете сделать явную реализацию интерфейса, и когда вы получите доступ к методу через IMyInterface.Search - компилятор запустит правильный метод.

0 голосов
/ 25 мая 2011

Надеюсь, я не растерялся, не могли бы вы изменить свои определения, например:

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

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

Предоставление общего аргумента T, который можно использовать для обеспечения того, чтобы реализация обеспечивала ограничение функции поиска для типов T:

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

    }
}

Таким образом, ваши производные типы просто:

public class MyDerivedClass : MyBaseClass
{

}

Что вы можете затем сделать поиск как:

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

var results = iface.Search<MyDerivedClass>();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...