Я хотел бы сохранить экземпляры SpecificResult
(и другие типы, производные от Result
) в файле List<Result>
. Каждый производный тип может содержать разные свойства. Я хотел бы иметь метод виртуального базового класса LimitsPassed
на Result
, который проверяет, соответствуют ли эти конкретные свойства некоторым критериям (например, значениям свойств из производных экземпляров Limits
, которые передаются в качестве параметра методу). Разные классы, производные от Results
, должны получать разные типы Limits
.
Я попытался переопределить метод LimitsPassed
с помощью более производного параметра SpecificLimits
.
Очевидно, что показанный здесь код не компилируется, поскольку тип производного класса не соответствует определению базового класса.
Каковы возможные решения для этого?
public abstract class Result
{
public abstract bool LimitsPassed(Limits limits)
}
public class SpecificResult : Result
{
public double MeasureA { get; set; }
public string MeasureB { get; set; }
public override bool LimitsPassed(SpecificLimits limits)
{
//specify parameter base type to particular type of derived class
return this.MeasureA >= limits.MeasureA_min
&& this.MeasureA <= limits.MeasureA_max
&& this.MeasureB == limits.MeasureB_master;
}
}
public abstract class Limits
{
}
public class SpecificLimits : Limits
{
public double MeasureA_min { get; set; }
public double MeasureA_max { get; set; }
public string MeasureB_master { get; set; }
}
Если я могу немного оспорить ваш дизайн... Что происходит, когда вы звоните LimitsPassed
на SpecificResult
и предоставляете Limits
, которые не являются SpecificLimits?
частью работы SpecificResult
(в силу того факта, что это Result
), так это то, что он должен обеспечивать реализацию LimitsPassed(Limits)
Какова связь между SpecificResult
и SpecificLimits
? Другими словами, почему SpecificResult
волнует, какой тип Limits
передается, если это Result
?
@Wyck Я хотел бы сохранить конкретные результаты (и другие типы, полученные из результатов) в списке <Results>. Каждый производный тип может содержать разные свойства. Метод LimitsPassed должен проверять, соответствуют ли эти свойства некоторым критериям (например, ограничениям). Различные классы, производные от результатов, будут иметь разные свойства и должны получать разные типы ограничений. И, наконец, LimitsPassed реализует логику для проверки соответствия свойств требованиям.
С точки зрения дизайна, это не похоже на SpecificLimits
, это Limits
, потому что вы говорите, что «Различные классы, производные от результатов, будут иметь разные свойства». Я говорю, что, возможно, LimitsPassed(Limits limits)
не следует быть членом Result
. Ваш вопрос должен служить своего рода мотивацией того, почему все Результаты должны иметь возможность выполнять LimitsPassed
произвольно Limits
. Я спрашиваю, потому что пытаюсь определить, нужен ли public new bool LimitsPassed(SpecificLimits limits)
(чтобы скрыть метод базового класса, но при этом предоставить реализацию)
@Wyck, я отредактировал свой вопрос и добавил некоторую реализацию классов. Я создал классыResults и Limits, потому что я хотел бы вызвать метод LimitsPassed с параметром базового класса и позволить программе решить, какой именно производный тип является точным во время выполнения. Я также пробовал использовать методы расширения, но не могу вызвать их и в базовом классе.
Этот вопрос похож на: Можно ли переопределить метод производным параметром вместо базового?. Если вы считаете, что это другое, отредактируйте вопрос, поясните, чем он отличается и/или как ответы на этот вопрос не помогают решить вашу проблему.
Подумайте или расскажите нам, как вы хотите это использовать. Если ваш код окружает List<Result>, вы, вероятно, в основном будете знать, что это Result, и вам будет трудно использовать более конкретную версию базового метода без постоянного знания/проверки/приведения к производному классу Result.
У вас есть несколько вариантов. Вероятно, вы хотите указать, что все производные классы, реализующие LimitsPassed
, используют параметр, производный от класса Limits
.
Пример:
public abstract class Result
{
public abstract bool LimitsPassed<T>(T limits) where T:Limits;
}
public class SpecificResult : Result
{
public override bool LimitsPassed<T>(T limits)
{
throw new NotImplementedException();
}
}
public abstract class Limits
{
}
public class SpecificLimits : Limits
{
}
Другой вариант заключается в том, имеет ли объект, переданный в LimitsPassed
, логически согласованный контракт, и в этом случае вы можете использовать подход на основе интерфейса.
Пример:
public abstract class Result
{
public abstract bool LimitsPassed(ILimits limits);
}
public class SpecificResult : Result
{
public override bool LimitsPassed(ILimits limits)
{
//specify parameter base type to particular type of derived class
}
}
public interface ILimits
{
}
public class SpecificLimits : ILimits
{
}
Во втором примере не имеет значения, является ли это интерфейсом. Вы могли бы оставить это как class Limits
вместо interface ILimits
. Важным аспектом вашего второго примера является то, что вы изменили тип аргумента на LimitsPassed
, чтобы принять тот же тип аргумента, что и метод базового класса.
@Вик Правда. Однако, поскольку предпочтительнее кодировать интерфейс, а не конкретизировать/реализовать, здесь показан интерфейс.
Обычной практикой является переопределение метода базового класса и определение другого метода для конкретного типа. Проверьте тип параметра в переопределенном методе и передайте его конкретному методу для дальнейшей обработки.
public class SpecificResult : Result
{
public override bool LimitsPassed(Limits limits)
=> limits is SpecificLimits specificLimits ?
LimitsPassed(specificLimits) :
throw new ArgumentException(); // or false
public bool LimitsPassed(SpecificLimits limits)
{
//specify parameter base type to particular type of derived class
}
}
Другой подход заключается в использовании отражения для приведения параметра из его базового типа в ожидаемый производный тип. Однако вам все равно придется обрабатывать случай, когда параметр не является ожидаемым типом.
public class SpecificResult : Result
{
public override bool LimitsPassed(Limits limits) {
if (limits is SpecificLimits specificLimits) {
// Do something with specificLimits
} else {
// TODO: Handle case where Limits provided are not SpecificLimits
throw new ArgumentException("Limits must be of type SpecificLimits");
}
}
}
Судя по комментариям и вашему вопросу, я не вижу ничего общего с Limits
, так что, возможно, у нас может быть базовый Result
класс, который будет выглядеть так (я сделал LimitsPassed
return null
вместо того, чтобы генерировать исключения для недопустимых типов пределов, но это вопрос выбора конечно)
public abstract class Result {
// can be bool but then we have to throw exceptions
// when invalid Limits type
public abstract bool? LimitsPassed(object limits);
// helps us when we have List<Result>
// so we can filter the results that support the limit
// check for certain Limit Types
public abstract Type[] AllowedLimitTypes { get; }
}
это будет обрабатывать случаи, когда у нас есть несколько возможных типов ограничений в наших производных классах.
У нас также может быть удобная универсальная версия для одного типа лимита, которую легко переопределить.
public abstract class Result<TLimit> : Result {
Type[] _types = new Type[] { typeof(TLimit) };
public override Type[] AllowedLimitTypes => _types;
// default implementation so we don't have to do it every time in our derived classes
// sealed override doesn't allow derived classes to break the contract
// of having just a single limit as generic parameter
// (for example by including checks for multiple limits types in this method)
// those classes should derive from the non-generic Result instead
public sealed override bool? LimitsPassed(object limits) {
if (limits is TLimit tLimit) return LimitsPassed(tLimit);
return null;
}
public abstract bool LimitsPassed(TLimit limits);
}
public class SpecificResult : Result<SpecificLimits> {
public double MeasureA { get; set; }
public string MeasureB { get; set; }
public override bool LimitsPassed(SpecificLimits limits) {
return this.MeasureA >= limits.MeasureA_min
&& this.MeasureA <= limits.MeasureA_max
&& this.MeasureB == limits.MeasureB_master;
}
}
Для нескольких типов лимитов у нас есть немного больше работы:
public class SpecificResult2 : Result {
Type[] _types = new Type[] { typeof(SpecificLimits), typeof(SpecificLimits2) };
public override Type[] AllowedLimitTypes => _types;
public override bool? LimitsPassed(object limits) {
return limits switch {
SpecificLimits specLimits => LimitsPassed(specLimits),
SpecificLimits2 specLimits2 => LimitsPassed(specLimits2),
_ => null
};
}
public bool LimitsPassed(SpecificLimits limits) {
// implementation
return true;
}
public bool LimitsPassed(SpecificLimits2 limits) {
// implementation
return true;
}
}
У нас также могут быть общие удобные классы для более распространенных случаев — два или три TLimits
:
public abstract class Result<TLimit1,TLimit2>
public abstract class Result<TLimit1,TLimit2,TLimit3>
Есть ли способ, который не позволяет использовать несколько типов Limits
в LimitsPassed
без создания общего класса? Я спрашиваю, потому что мой дизайн предполагает, что класс SpecificResult
всегда будет ссылаться на SpecificLimits
. Например, SpecificResultA
всегда будет относиться к SpecificLimitsA
, SpecificResultB
к SpecificLimitsB
и так далее. Мне просто интересно, верна ли моя конструкция. Для меня лучшим подходом без создания общего класса является ответ @Wyck, однако он не защищает меня от передачи несвязанного класса SpecificLimits
в метод LimitsPassed
. Спасибо.
@oleq общий Result<TLimit>
- это скорее удобный класс, который инкапсулирует логику приведения (из ответа @Wyck). В то же время у вас есть то, что вам действительно нужно — более производный тип, принимающий LimitsPassed. Types[]
позволяет легко фильтровать List<Result>
и применять более производные функции...
также с помощью общего класса вы можете легко фильтровать список результатов, т. е. is Result<SpecificLimits>
это также дает вам возможность использовать любые объекты (включая структуры) для Liimts
-подобных объектов, поэтому вам не придется выполнять там бессмысленное наследование, как это было прокомментировано под вашим вопросом.
спасибо за ответ. Я проанализировал ваш подход и считаю, что это лучшая реализация в моем случае.
Похоже, ваш класс
Result
, вероятно, должен быть универсальным по типу ограничения, которое он может получить. (например,class Result<TLimit> where TLimit : Limits
). ТогдаSpecificResult : Result<SpecificLimits>
...