C#8.0: Защищенные свойства для интерфейсов с реализациями по умолчанию
Скажем, у меня есть следующие интерфейсы:
public interface IReturnableAs {
protected String ReturnAs { get; set; }
}
public interface IReturnableAsImage<T> {
protected String ImageResolution { get; set; }
public T ReturnAsImage(String imageResolution = "large") {
ReturnAs = "image";
ImageResolution = imageResolution;
return (T)this;
}
}
public interface IReturnableAsJson<T> {
protected Boolean IsPretty { get; set; }
public T ReturnAsJson(Boolean isPretty = false) {
ReturnAs = "json";
IsPretty = isPretty;
return (T)this;
}
}
public class Foo : IReturnableAsImage<Foo>, IReturnableAsJson<Foo> {...}
Изначально ошибки вынуждают меня иметь реализации для ReturnAs
, ImageResolution
а также IsPretty
свойства. Выполнение этих реализацийprotected
закончить с CS0535
, говоря, что свойства не были реализованы. С другой стороны, делая этиpublic
дал мне CS8704
, говоря, что такая реализация невозможна.
Существуют ли способы обхода этих ошибок, кроме абстрактных классов?
2 ответа
Прежде всего, чего вы хотите добиться? Что должноFoo.ReturnAs
возвращение? Как вы собираетесь использовать эти интерфейсы?
Вы не можете использовать ReturnAs
в других интерфейсах без наследования от IReturnableAs
. Как только вы унаследуете этот интерфейс,Foo
придется предоставить реализацию. Когда это произойдет, как бы вы ни бросалиFoo
у тебя всегда будет свое IReturnableAs
реализация.
Интерфейсы не являются абстрактными классами, поэтому может быть только одна реализация члена интерфейса. Вы не можете получить доступ к различным реализациям "по умолчанию" через разные интерфейсы.
Составной результат при доступе через IReturnableAs
Если ты хочешь вернуться json
или image
для конкретных интерфейсов и image;json
за Foo
в целом лучшим вариантом было бы, чтобы интерфейсы не наследовали отIReturnableAs
, и предоставить свои ReturnAs
свойство:
public interface IReturnableAs {
public String ReturnAs { get; }
}
public interface IReturnableAsImage<T>
{
public String ReturnAs =>"image";
protected String ImageResolution { get; set; }
public T ReturnAsImage(String imageResolution = "large")
{
ImageResolution = imageResolution;
return (T)this;
}
}
public interface IReturnableAsJson<T> {
public String ReturnAs =>"json";
protected Boolean IsPretty { get; set; }
public T ReturnAsJson(Boolean isPretty = false) {
IsPretty = isPretty;
return (T)this;
}
}
public class Foo : IReturnableAsImage<Foo>, IReturnableAsJson<Foo> ,IReturnableAs
{
string IReturnableAs.ReturnAs =>"image;json";
String IReturnableAsImage<Foo>.ImageResolution { get; set; }="3";
Boolean IReturnableAsJson<Foo>.IsPretty { get; set; }=false;
}
Следующий код:
void Main()
{
var foo=new Foo();
Console.WriteLine(((IReturnableAs)foo).ReturnAs);
Console.WriteLine(((IReturnableAsImage<Foo>)foo).ReturnAs);
Console.WriteLine(((IReturnableAsJson<Foo>)foo).ReturnAs);
}
Печать:
image;json
image
json
Я удалил ReturnAs
сеттеры, поскольку действительное значение всегда будет одинаковым для одного и того же интерфейса.
Если вы хотите создать новый класс, который генерирует файлы JPG, например FooJpg
, вы можете переопределить реализацию по умолчанию IReturnableAsImage<T>
, например:
public class FooJpg : IReturnableAsImage<FooJpg>, IReturnableAsJson<FooJpg> ,IReturnableAs
{
string IReturnableAs.ReturnAs =>"jpg;json";
String IReturnableAsImage<FooJpg>.ImageResolution { get; set; }="3";
Boolean IReturnableAsJson<FooJpg>.IsPretty { get; set; }=false;
String IReturnableAsImage<FooJpg>.ReturnAs => "jpg";
}
Тот же результат независимо от интерфейса
Если ты хочешь Foo.ReturnAs
чтобы всегда возвращать одно и то же значение, например "image;json"
, вы можете добавить значение по умолчанию IReturnAs
реализация для единичных случаев использования и переопределение метода для многократного использования:
public interface IReturnableAs {
public String ReturnAs { get; }
}
public interface IReturnableAsImage<T>:IReturnableAs
{
String IReturnableAs.ReturnAs =>"image";
protected String ImageResolution { get; set; }
public T ReturnAsImage(String imageResolution = "large")
{
ImageResolution = imageResolution;
return (T)this;
}
}
public interface IReturnableAsJson<T>:IReturnableAs {
String IReturnableAs.ReturnAs =>"json";
protected Boolean IsPretty { get; set; }
public T ReturnAsJson(Boolean isPretty = false) {
//ReturnAs="json";
IsPretty = isPretty;
return (T)this;
}
}
В этом случае IReturnableAsImage
, IReturnableAsJson
интерфейсы обеспечивают реализацию. Для этого класса:
public class Foo : IReturnableAsImage<Foo>
{
String IReturnableAsImage<Foo>.ImageResolution { get; set; }="3";
}
Следующий код напечатает image
:
void Main()
{
var foo=new Foo();
Console.WriteLine(((IReturnableAs)foo).ReturnAs);
Console.WriteLine(((IReturnableAsImage<Foo>)foo).ReturnAs);
}
Для класса, использующего оба интерфейса, явное IReturnableAs
реализация необходима:
public class FooMulti : IReturnableAsImage<FooMulti>, IReturnableAsJson<FooMulti>
{
String IReturnableAs.ReturnAs =>"image;json";
String IReturnableAsImage<FooMulti>.ImageResolution { get; set; }="3";
Boolean IReturnableAsJson<FooMulti>.IsPretty { get; set; }=false;
}
В этом случае все звонки вернутся image;json
:
void Main()
{
var foo=new FooMulti();
Console.WriteLine(((IReturnableAs)foo).ReturnAs);
Console.WriteLine(((IReturnableAsImage<FooMulti>)foo).ReturnAs);
Console.WriteLine(((IReturnableAsJson<FooMulti>)foo).ReturnAs);
}
image;json
image;json
image;json
Здесь есть решение, хотя я не уверен, нравится ли оно мне.
и может расширять и скрывать свой метод с помощью
new
метод.
Они могут и, вероятно, должны также явно переопределить свойство , чтобы избавить конкретную реализацию от необходимости делать это; см. реализацию ниже.
Поскольку реализует оба
IReturnableAsImage<T>
а также
IReturnableAsJson<T>
тем не менее, он также должен будет обеспечить реализацию.
public interface IReturnableAs
{
public String ReturnAs { get; }
}
public interface IReturnableAsImage<T> : IReturnableAs
{
// implicitly "override" IReturnableAs's ReturnAs
string IReturnableAs.ReturnAs => ReturnAs;
// use "new" to indicate hiding on purpose
public new string ReturnAs => "image";
protected string ImageResolution { get; set; }
public T ReturnAsImage(String imageResolution = "large")
{
ImageResolution = imageResolution;
return (T)this;
}
}
public interface IReturnableAsJson<T> : IReturnableAs
{
// implicitly "override" IReturnableAs's ReturnAs
string IReturnableAs.ReturnAs => ReturnAs;
// use "new" to indicate hiding on purpose
public new string ReturnAs => "json";
protected bool IsPretty { get; set; }
public T ReturnAsJson(Boolean isPretty = false)
{
isPretty = isPretty;
return (T)this;
}
}
не нужно реализовывать, как это уже делается. Хотя это может «перегружать» (переопределение может быть лучшим термином) неявно или либо, либо
IReturnableAsImage<Bar>.ReturnAs
явно, в любом сочетании.
public class Bar : IReturnableAsImage<Bar>
{
// public string ReturnAs => "implicit ReturnAs";
// string IReturnableAs.ReturnAs => "explicit IReturnableAs.ReturnAs";
// string IReturnableAsImage<Bar>.ReturnAs => "explicit IReturnableAsImage<Bar>.ReturnAs";
string IReturnableAsImage<Bar>.ImageResolution { get; set; } = "3";
}
с другой стороны, необходимо явно переопределить
IReturnableAs.ReturnAs
явно или неявно (или и то, и другое), поскольку и IReturnableAsImage, и IReturnableAsJson предоставляют реализации для этого свойства.
public class Foo : IReturnableAsImage<Foo>, IReturnableAsJson<Foo>
{
// public string ReturnAs => "implicit";
// string IReturnableAsImage<Bar>.ReturnAs => "explicit image";
// string IReturnableAsJson<Bar>.ReturnAs => "explicit json";
string IReturnableAs.ReturnAs => "image;json";
string IReturnableAsImage<Foo>.ImageResolution { get; set; } = "3";
bool IReturnableAsJson<Foo>.IsPretty { get; set; } = false;
}
Результаты работают так, как ожидалось, хотя изменение того, что переопределяют конкретные методы, изменит результаты, возможно, неожиданно.
void Main()
{
var bar = new Bar();
Console.WriteLine("Bar: ");
Console.WriteLine(((IReturnableAs)bar).ReturnAs + " - (IReturnableAs)" );
Console.WriteLine(((IReturnableAsImage<Bar>)bar).ReturnAs + " -
(IReturnableAsImage<Bar>)");
// only works when ReturnAs is explicitly implemented on Bar
// Console.WriteLine(bar.ReturnAs + " - (Bar)");
Console.WriteLine();
var foo = new Foo();
Console.WriteLine("Foo:");
Console.WriteLine(((IReturnableAs)foo).ReturnAs + " - (IReturnableAs)");
Console.WriteLine(((IReturnableAsImage<Foo>)foo).ReturnAs + " - (IReturnableAsImage<Foo>)");
Console.WriteLine(((IReturnableAsJson<Foo>)foo).ReturnAs + " - (IReturnableAsJson<Foo>)");
// only works when ReturnAs is explicitly implemented on Foo
// Console.WriteLine(foo.ReturnAs + " - (Foo)");
}
Выход:
Bar:
image - (IReturnableAs)
image - (IReturnableAsImage<Bar>)
Foo:
image;json - (IReturnableAs)
image - (IReturnableAsImage<Foo>)
json - (IReturnableAsJson<Foo>)
Вы можете прокомментировать закомментированные строки
Foo
и выше, чтобы увидеть, что и когда перегружается.
Например, если явно реализует
ReturnAs
он переопределяет реализации по умолчанию обоих
IReturnableAs
а также
IReturnableAsImage<Bar>
пока не
Bar
также неявно реализует их.