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также неявно реализует их.

Другие вопросы по тегам