b12629
b12629

Reputation: 85

C#8.0: Protected properties for interfaces with default implementations

Say I have the following interfaces:

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> {...}

Initially, the errors force me to have implementations for the ReturnAs, ImageResolution and IsPretty properties. Making these implementations protected end up with CS0535, saying that the properties weren't implemented. On the other hand, making these public gave me CS8704, telling that this implementation is not possible.

Other than abstract classes, would there be workarounds for these errors?

Upvotes: 1

Views: 2170

Answers (2)

Kyle Shanafelt
Kyle Shanafelt

Reputation: 949

There is a solution here, though I am not sure if I like it.

IReturnableAsImage<T> and IReturnableAsJson<T> can extend IReturnableAs and hide its ReturnAs method with a new ReturnAs method.

They can, and probably should, also explicitly override IReturnableAs's ReturnAs property to save a concreate implementation from having to do so; see the implementation of Bar below.

Since Foo implements both IReturnableAsImage<T> and IReturnableAsJson<T> though, it will have to provide an implementation for IReturnableAs.ReturnAs as well.

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;
    }
}

Bar doesn't need to implement IReturnableAs.ReturnAs as IReturnableAsImage<Bar> already does this. Though it may to "overload" (reimplement may be a better term) ReturnAs implicitly or either IReturnableAs.ReturnAs or IReturnableAsImage<Bar>.ReturnAs explicitly, in any combination.

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";
}

Foo on the other hand must explicitly reimplement IReturnableAs.ReturnAs implicitly or ReturnAs explicitly (or both) since both IReturnableAsImage and IReturnableAsJson provide implementations for this property.

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;
}

The results work as expected, though changing what the concrete methods reimplement will change the results, possibly unexpectedly.

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)");  
     }

Output:

Bar:
image -  (IReturnableAs)
image -  (IReturnableAsImage<Bar>)

Foo:
image;json -  (IReturnableAs)
image -  (IReturnableAsImage<Foo>)
json -  (IReturnableAsJson<Foo>)

You may want to comment in the commented out lines of Foo and Bar above to see what gets overloaded and when.

For example, if Bar explicitly implements ReturnAs it overrides default implementations of both IReturnableAs and IReturnableAsImage<Bar> unless Bar implicitly implements them as well.

Upvotes: 0

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131324

First of all, what do you want to achieve? What should Foo.ReturnAs return? How are you going to use those interfaces?

You can't use ReturnAs in the other interfaces without inheriting from IReturnableAs. Once you inherit from that interface though Foo will have to provide an implementation. When that happens, no matter how you cast Foo you'll always get its own IReturnableAs implementation.

Interfaces aren't abstract classes so there can only be one interface member implementation. You can't access different "default" implementations through different interfaces.

Composite result when accessed through IReturnableAs

If you want to return json or image for the specific interfaces and image;json for Foo overall the best option would be for the interfaces to not inherit from IReturnableAs, and provide their own ReturnAs property :

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;
}

The following code :

void Main()
{
  var foo=new Foo();
  Console.WriteLine(((IReturnableAs)foo).ReturnAs);
  Console.WriteLine(((IReturnableAsImage<Foo>)foo).ReturnAs);
  Console.WriteLine(((IReturnableAsJson<Foo>)foo).ReturnAs); 
}

Prints:

image;json
image
json

I removed the ReturnAs setters since the valid value will always be the same for the same interface.

If you want to create a new class that generates JPGs, eg FooJpg, you can override the default implementation of IReturnableAsImage<T>, eg :

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";

}

Same result no matter the interface

If you want Foo.ReturnAs to always return the same value, eg "image;json", you can add a default IReturnAs implementation for single use cases, and override the method for multiple uses :

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;
  }
}

In this case the IReturnableAsImage, IReturnableAsJson interfaces provide an implementation. For this class :

public class Foo : IReturnableAsImage<Foo>
{
    String IReturnableAsImage<Foo>.ImageResolution { get; set; }="3";
}

The following code will print image:

void Main()
{
  var foo=new Foo();
  Console.WriteLine(((IReturnableAs)foo).ReturnAs);
  Console.WriteLine(((IReturnableAsImage<Foo>)foo).ReturnAs);
}

For a class that uses both interfaces, an explicit IReturnableAs implementation is needed:

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;
}

In this case all calls will return 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

Upvotes: 1

Related Questions