Взаимозависимые интерфейсы с использованием обобщений в C # - PullRequest
0 голосов
/ 20 декабря 2011

Приношу свои извинения, если это сильно сбивает с толку.

У меня есть два основных интерфейса.Первый интерфейс ISensor:

public interface ISensor<TReading>
    where TReading : ISensorReading<ISensor<TReading>>
{
    event SensorReadingCompletedEH<ISensor<TReading>, TReading> ReadCompleted;
    TReading Read();
}

И второй интерфейс: ISensorReading:

public interface ISensorReading<TSensor>
    where TSensor : ISensor<ISensorReading<TSensor>>
{
    TSensor Sensor { get; }
}

Это приводит к следующим ошибкам:

Тип ISensor должен быть преобразован вISensor> чтобы использовать его в качестве параметра TSensor в универсальном типе или методе ISensorReading

и

Тип ISensorReading должен быть преобразован вISensorReading> чтобы использовать его в качестве параметра TReading в универсальном типе или методе ISensor

Боюсь, это связано с неразрешимой циклической ссылкой во время компиляции;однако я хочу обеспечить соответствие для производных типов, таких как TelemetricSensor: ISensor<TelemetricReading> и TelemetricReading: ISensorReading<TelemetricSensor>

Какие другие подходы я должен использовать, чтобы разрешить простое приведение типов и безопасность типов?.NET 2.0 и VS2005

Ответы [ 3 ]

1 голос
/ 20 декабря 2011

Скажем, вы определяете два типа:

    class AReading : ISensorReading<ASensor> { }
    class ASensor : ISensor<AReading> { }

Теперь объявление ISensorReading<ASensor> недопустимо, потому что ASensor НЕ реализует ISensor<ISensorReading<ASensor>>. Вместо этого он реализует ISensor<Areading>, который отличается.

См. В .NET утверждение A : B обычно не подразумевает утверждение I<A> : I<B>. И если вы думаете об этом тщательно, это не обязательно верно - зависит от природы I.

Функции, которые вы ищете, называются "Ковариация" и "Контравариантность". Это еще одна особенность C #, где вы можете сказать компилятору, что для вашего конкретного интерфейса I вышеупомянутое следствие действительно выполняет (ковариация), или что обратное значение I<B> : I<A> (контравариантность).

Первое, что вы достигли, используя ключевое слово out:

    interface I<out T> { ... }

Второй - с помощью ключевого слова in:

    interface I<in T> { ... }

К сожалению, однако, ковариация и контравариантность в параметрах универсального типа были введены только в C # 4.0, так что вам здесь не повезло.

Вы можете перейти на C # 4.0 (что я настоятельно рекомендую) или вы можете положиться на свои модульные тесты, чтобы убедиться, что все типы остаются связными.

1 голос
/ 20 декабря 2011

Вы правы - вы не можете определить их, используя ограничения универсального типа таким образом, потому что это приводит к бесконечному вложению.:

public interface ITelemetricSensorReading : ISensorReading

и затем ограничить датчик, чтобы обеспечить реализацию этого:

public interface ITelemetricSensor<TReading> : ISensor<TReading> where TReading : ITelemetricSensorReading
0 голосов
/ 22 января 2012

Спасибо всем за ваши ответы.

Наконец, это решение, которое я выбрал для этой проблемы.Надеюсь быть полезным.Если у вас есть комментарии или вы знаете способ улучшить его без изменения структуры, дайте мне знать.

Сначала я создал интерфейс ISensor с минимальными требованиями.После этого я определил новый универсальный интерфейс ISensor следующим образом:

public delegate void SensorErrorEventHandler<TSensor>(TSensor sensor, ISensorError error)
    where TSensor : ISensor;

public delegate void SensorReadingCompletedEventHandler<TSensor, TReading>(TSensor sensor, TReading[] read)
    where TSensor : ISensor
    where TReading : ISensorReading<TSensor>;

public interface ISensor : IDisposable
{
    bool IsOpen { get; }
    bool Started { get; }
    void Connect();
    void Disconnect();
    void Start();
    void Stop();
}

public interface ISensor<TSensor, TReading> : ISensor
    where TSensor : ISensor
    where TReading : ISensorReading<TSensor>
{
    TReading[] LastReadings { get; }
    event SensorErrorEventHandler<TSensor> Error;
    event SensorReadingCompletedEventHandler<TSensor, TReading> ReadCompleted;
    bool Read(out TReading[] readings);
}

public interface ISensorReading<TSensor> where TSensor : ISensor
{
    TSensor Sensor { get; }
    bool Mistaken { get; }
}

С этими интерфейсами, определенными и повторяющими те же структуры, я смог создать первую реализацию: класс TelemetricSensor с соответствующим ему ITelemetricReading

public delegate void TelemetricSensorThresholdExceededEventHandler<TSensor>(TSensor sensor)
    where TSensor : ITelemetricSensor;

public interface ITelemetricSensor : ISensor
{
    /* Properties, events and methods */
}

public interface ITelemetricReading : ISensorReading<ITelemetricSensor>
{
    /* Properties, events and methods */
}

public abstract class TelemetricSensor<TSensor, TReading> : ITelemetricSensor, ISensor<TSensor, TReading>
    where TSensor : ITelemetricSensor
    where TReading : ITelemetricReading, ISensorReading<TSensor>
{
    public abstract TReading[] LastReadings { get; }
    public event SensorErrorEventHandler<TSensor> Error;
    public event SensorReadingCompletedEventHandler<TSensor, TReading> ReadCompleted;

    public abstract bool Read(out TReading[] readings);
}

Здесь интересен TReading абстрактного класса TelemetricSensor.Он определен как

TReading : ITelemetricReading, ISensorReading<TSensor>

, что может показаться избыточным, поскольку ITelemetricReading наследуется от ISensorReading, но требуется для получения кода, скомпилированного и удовлетворяющего требованию как для свойства

TReading[] LastReadings { get; }

и

bool Read(out TReading[] readings);

метод.Наконец, чтобы пропустить утомительное приведение (и, возможно, некоторые ошибки), можно выполнить многократные перегрузки Read (...), чтобы каждый из них предоставил правильно приведенные данные и также соответствовал всем реализациям интерфейса.

...