Как я могу вернуть более производный тип из интерфейса?

#c# #generics #interface #covariance

#c# #обобщения #интерфейс #ковариация

Вопрос:

У меня есть интерфейс, который преобразует сетку данных, которую я хочу реализовать:

 public interface ITransformable<T>
{
    T Slice(int x, int y, int width, int height);
    T Slice(Rectangle value);
    T Transpose();
    T Flip(bool horizontal, bool vertical);
    T Rotate(bool clockwise);
}
  

Итак, я создаю класс, который делает это:

 public class Mesh<T> : ITransformable<Mesh<T>>
{
    public Mesh<T> Transpose() { ... }
    /// etc
}
  

Однако при создании более производной версии mesh я сталкиваюсь с проблемой. Например, у меня есть Heightmap класс, который является Mesh<float> . Создавая конкретную реализацию T, это позволяет мне использовать перегрузки операторов, чтобы я мог легко добавлять, например, две карты высот вместе. Но когда я его реализую

 public class Heightmap : Mesh<float> { ... }
  

Heightmap функции из ITransformable по-прежнему возвращают сетку, а не карту высот. Есть ли какой-либо способ реализовать базовое поведение в Mesh , но «изменить» возвращаемый тип в более производном классе? Я думал, что это цель ковариации, но, похоже, я не могу понять это.

Комментарии:

1. Если вы собираетесь переопределить Transpose , вам нужно сделать это virtual в Mesh<T> , затем override Heightmap Slice(int x, int y, int width, int height) {...} внутри Heightmap . Если вы уже пробовали это, пожалуйста, покажите сообщение об ошибке или то, как это не работает в вашем вопросе.

2. Возможно, вам нужно передать способ создания производных объектов в базовый класс Mesh<T,TImpl> : ITransformable<TImpl> where TImpl:Mesh<T>, new()

3. Несмотря на то, что интерфейс требует возврата a Mesh<T> , реализация все равно может возвращать a Heightmap из переопределенной версии Transpose (хотя это необходимо отметить virtual ). Вызывающий объект должен будет выполнить приведение к Heightmap , если ему нужен доступ к определенным свойствам / методам этого класса.

4. Похоже, у @luqui был ответ, который я искал. Просто переопределяя метод и возвращая base.Transpose() корректное изменение ввода, которое я хотел. Я чувствую себя глупо, спрашивая сейчас, но, по крайней мере, это работает!

5. Кайл Баран: похоже, то, что вы просили, не соответствует тому, что вы хотели … или, может быть, в вашем комментарии используются неправильные термины — вы не можете переопределить метод и изменить возвращаемый тип… В любом случае — рассмотрите возможность публикации ответа.

Ответ №1:

Пожалуйста, попробуйте следующий код, он должен работать как шарм. Замените методы / исключения на вашу собственную реализацию.

   public interface ITransformable<T, out S> where S : ITransformable<T, S>
    {
        S Slice(int x, int y, int width, int height);
        S Slice(Rectangle value);
        S Transpose();
        S Flip(bool horizontal, bool vertical);
        S Rotate(bool clockwise);
    }

    public class Mesh<T, S> : ITransformable<T, S> where S : Mesh<T,S>, ITransformable<T, S>, new()
    {
        public S Slice(int x, int y, int width, int height)
        {
            throw new NotImplementedException();
        }

        public S Slice(Rectangle value)
        {
            throw new NotImplementedException();
        }

        public S Transpose()
        {
           //The following will work smoothly.
           S sObject = this.Slice(10, 20, 30, 40);
           return sObject;
        }
        public S Flip(bool horizontal, bool vertical)
        {
            throw new NotImplementedException();
        }

        public S Rotate(bool clockwise)
        {
            throw new NotImplementedException();
        }

    }

    public class Heightmap : Mesh<float, Heightmap>
    {

    }

    public class Program
    {
        static void Main()
        {
            Heightmap heightmap = new Heightmap();
            Heightmap map2 = heightmap.Transpose(); //This will work smoothly.
        }
    }