Универсальный тип для свойства интерфейса

#c# #generics #interface

#c# #общие #интерфейс

Вопрос:

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

  • различные точки данных, которые реализуют IDataPoint интерфейс (для обеспечения существования значения и точки координат)
  • каждая точка данных может иметь разные типы координат (2D, 3D и т.д.)
  • координаты хранятся в общих кортежах, которые все реализуют интерфейс ITuple (для обеспечения существования хотя бы одной (X) координаты)

Моя проблема в том, что я не выяснил, как создать Coord свойство IDataPoint универсального, чтобы координаты могли быть либо 2d, либо 3d (или что-то еще, если потребуется позже). Это моя попытка:

 public interface IDataPoint
{ 
    float Value { get; }
    <Tuple> Coords { get; } where <ITuple> : ITuple

    string ToString();
}
  

Где моя ошибка или это просто невозможно?

Остальная часть кода

 public interface ITuple<T>
{
    T X { get; }

    string ToString();
}

public struct TwoTuple<T> : ITuple<T>
{
    public T X { get; }
    public T Y { get; }
    public TwoTuple(T x, T y)
    {
        X = x;
        Y = y;
    }

    public override string ToString()
    {
        return "("   X   ", "   Y   ")";
    }
}

public struct ThreeTuple<T> : ITuple<T>
{
    public T X { get; }
    public T Y { get; }
    public T Z { get; }
    public ThreeTuple(T x, T y, T z)
    {
        X = x;
        Y = y;
        Z = z;
    }

    public override string ToString()
    {
        return "("   X   ", "   Y   ", "   Z   ")";
    }
}

public interface IDataPoint
{ 
    float Value { get; }
    <Tuple> Coords { get; } where <ITuple> : ITuple

    string ToString();
}

public struct BarDataPoint : IDataPoint
{
    public TwoTuple<float> Coords { get; }
    public float Value { get; }
    public BarDataPoint(TwoTuple<float> Coords, float Value)
    {
        this.Coords = Coords;
        this.Value = Value;
    }

    public override string ToString()
    {
        return "Coordinates: "   Coords   "; Value: "   Value;
    }
}

public struct ScatterDataPoint : IDataPoint
{
    public ThreeTuple<float> Coords { get; }
    public float Value { get; }
    public ScatterDataPoint(ThreeTuple<float> coords, float value)
    {
        this.Coords = coords;
        this.Value = value;
    }

    public override string ToString()
    {
        return "Coordinates: "   Coords   "; Value: "   Value;
    }
}
  

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

1. public interface IDataPoint<T> { ITuple<T> Coords { get; } } ?

2. @JohnathanBarclay в нем говорится, что <T> отсутствует в пространстве имен. Если я добавлю его через IDataPoint<T> , это сработает, но затем реализация снова не сработает. Потому что, если я тогда скажу BarDataPoint : IDataPoint<float> , что это выдает ошибку, потому что возвращаемый тип для BarDataPoint is TwoTuple и not ITuple

Ответ №1:

Интерфейс

 public interface IDataPoint<TTuple>
{
    float Value { get; }
    TTuple Coords { get; }

    string ToString();
}
  

Использование

  public struct BarDataPoint : IDataPoint<ThreeTuple<float>>
{
    public float Value { get; }

    public ThreeTuple<float> Coords { get; set; }

    public BarDataPoint(ThreeTuple<float> Coords, float Value)
    {
        this.Coords = Coords;
        this.Value = Value;
    }

    public override string ToString()
    {
        return "Coordinates: "   Coords   "; Value: "   Value;
    }
}
  

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

1. Это работает! Большое спасибо! Остался один вопрос: как мне теперь использовать это в функции? public void ShowData<TDataPoint>(TDataPoint datapoint) where TDataPoint : IDataPoint<TwoTuple<float>> работает, но я хотел бы сделать TwoTuple универсальный.

2. При таком подходе вы отказываетесь от ограничения, которое тип Coords должен реализовать ITuple интерфейс.

3. @tadeseus Как насчет этого? public static void ShowData<T>(IDataPoint<T> dataPoint) where T: ITuple<float> { //loqic }

4. Это означает, что точки данных 2D и 3D теперь являются совершенно разными типами, которые нельзя обрабатывать унифицированным способом.

5. @AdiletB это снова работает! Ты мой спаситель. Однако есть (конечно) последняя проблема. Классы, использующие эти типы данных, больше не работают.. public class ScatterPlot : Plot<ScatterDataPoint> не работает, потому что «Нет преобразования бокса из’ScatterDataPoint’ в’IDataPoint'».

Ответ №2:

Сделайте это так, как вы делали это в своем ITuple интерфейсе, указав общие элементы и их ограничения в объявлении интерфейса:

 public interface IDataPoint<T,T2> where T : ITuple<T2>
{ 
    float Value { get; }
    T Coords { get; } 

    string ToString();
}
  

Coords Теперь ваше свойство имеет тип T и T должно реализовывать интерфейс ITuple<T2> . Ваш класс BarDataPoint должен быть определен следующим образом:

 public struct BarDataPoint : IDataPoint<TwoTuple<float>,float>
  

Вам придется дважды указать компилятору общий тип ITuple<T> . Онлайн-демонстрация: https://dotnetfiddle.net/CYw1bR

Ответ №3:

Что вы можете сделать, так это:

 public interface IDataPoint 
{
    float Value { get; }
    ITuple Coords { get; }

    string ToString();
}
  

вам не нужны обобщения. ITuple является базовой реализацией общего назначения для кортежей.
все они являются производными от ITuple

 System.Tuple<T1>
System.Tuple<T1,T2>
System.Tuple<T1,T2,T3>
System.Tuple<T1,T2,T3,T4>
System.Tuple<T1,T2,T3,T4,T5>
System.Tuple<T1,T2,T3,T4,T5,T6>
System.Tuple<T1,T2,T3,T4,T5,T6,T7>
System.Tuple<T1,T2,T3,T4,T5,T6,T7,TRest>
System.ValueTuple
System.ValueTuple<T1>
System.ValueTuple<T1,T2>
System.ValueTuple<T1,T2,T3>
System.ValueTuple<T1,T2,T3,T4>
System.ValueTuple<T1,T2,T3,T4,T5>
System.ValueTuple<T1,T2,T3,T4,T5,T6>
System.ValueTuple<T1,T2,T3,T4,T5,T6,T7>
System.ValueTuple<T1,T2,T3,T4,T5,T6,T7,TRest>
  

Это простая документация

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

1. ITuple является универсальным.

2. Это интерфейс. не универсальный

3. ITuple как определено OP, является общим.

4. мой ITuple не совпадает с его ITuple. мой — это интерфейс

5. OPs ITuple также является интерфейсом. OPs ITuple также определяет свойство X , которое, как я полагаю, является обязательным.