C# interface implementation with an interface property
Asked Answered
B

2

6

I am starting to dive a bit further into C# programming and have been messing around with interfaces.

I understand the basics of an interface in that they are supposed to be "contracts" for any class that implements them. Whatever is defined in the interface needs to be implemented in any class that inherits (not sure if that is the right term) from them.

So I added a property to an interface that is an interface in itself. When trying to implement the parent interface, I added a property to my class which is a class that implements the interface that is a property in the parent interface. That explanation might be a bit confusing so I added some code below.

interface IPropertyThatIsAnInterface
{
    public int X { get; set; }
}

class ClassThatImplementsIPropertyThatIsAnInterface : IPropertyThatIsAnInterface
{
    public int X { get; set; }
}
interface IAnInterface
{
    public IPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

class ClassThatImplementsIAnInterface : IAnInterface
{
    public ClassThatImplementsIPropertyThatIsAnInterface InterfaceImplmentationProperty { get; set; }
}

Visual Studio (in a .Net Core 3.0 [i.e. C# 8.0]) comes up with an error saying that my class, ClassThatImplementsIAnInterface, does not implement the interface property, public IPropertyThatIsAnInterface InterfaceProperty { get; set; }, (which is an interface property). Is this a limitation of C# or a limitation of my understand of interfaces?

The reason I am doing this is because I want to implement customised IPropertyThatIsAnInterfaces for each ClassThatImplementsIAnInterface I create. I.e.

class AnotherClassThatImplementsIPropertyThatIsAnInterface : IPropertyThatIsAnInterface
{
   public int X { get; set; }
   public int Z { get; set; }
}

I can work around this by doing the below

class ClassThatImplementsIAnInterface : IAnInterface
{
    public IPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

but the issue with this is that I need to cast each call to InterfaceProperty in my class definition when accessing that property i.e. ((ClassThatImplementsIPropertyThatIsAnInterface)InterfaceProperty).Y whenever I access one of the customised class properties. This is OK but not that nice.

So again, is this a C# limitation or a limitation of my understanding?

Bolin answered 28/11, 2019 at 1:11 Comment(5)
Interfaces do not use access modifiers, don't worry, it happens to all of usEversole
Read up on generics. That might be what you are looking for.Glad
@Eversole comment is somewhat incorrect. C# 8.0 (which the question specifically mentions) allows "default interface methods", which do include an access modifier in the interface. They're essentially syntactic sugar over extension methods, though, and auto-properties cannot be defined that way... so the example interface code is invalid regardless.Nunnally
@xander, could you please elaborate on this? Is defining the interface in this way incorrect { get;set; }? I think I see where you're coming from because I tried to define a default getter in the interface but it didn't work as I expected. If auto-properties cannot be defined in this way, then I guess that's why it doesn't work?Bolin
Turns out I was also somewhat incorrect--I was reading a C# 8.0 proposal, and the actual spec ended up being different. I should have compiled your code before commenting. In C# 8.0, you can include a property with a public modifier and no implementation in an interface. As far as I can tell, it's effectively the same as defining that property without the public modifier (in other words, int MyInt { get; set; } and public int MyInt { get; set; } are equivalent in an interface). What you've got is technically valid, but most C# devs would not include the public modifier in an interface.Nunnally
Z
6

This is valid C#:

interface IAnInterface
{
    IPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

interface IPropertyThatIsAnInterface
{
    int X { get; set; }
}

class ClassThatImplementsIAnInterface : IAnInterface
{
    public IPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

You could do this

class ClassThatImplementsIAnInterface : IAnInterface
{
    public ClassThatImplementsIPropertyThatIsAnInterface InterfaceImplmentationProperty { get; set; }

    public IPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

class ClassThatImplementsIPropertyThatIsAnInterface : IPropertyThatIsAnInterface
{
    public int X { get; set; }
}

Please note that InterfaceImplmentationProperty has nothing to do with IAnInterface.

We can make implement InterfaceProperty explicitly, which hides it:

IPropertyThatIsAnInterface IAnInterface.InterfaceProperty { get; set; }

Yet, InterfaceProperty and InterfaceImplmentationProperty are still separate. Let us delegate InterfaceProperty to InterfaceImplmentationProperty:

IPropertyThatIsAnInterface IAnInterface.InterfaceProperty
{
    get => InterfaceImplmentationProperty;
    set => InterfaceImplmentationProperty = value; // ERROR
}

Now, we have an error. For, you see, an IPropertyThatIsAnInterface is not necesarily an InterfaceImplmentationProperty. However, IAnInterface promises I can set any IPropertyThatIsAnInterface there.

If we continue this path, we got to subvert expectation, and throw an exception:

IPropertyThatIsAnInterface IAnInterface.InterfaceProperty
{
    get => InterfaceImplmentationProperty;
    set => InterfaceImplmentationProperty = (ClassThatImplementsIPropertyThatIsAnInterface)value;
}

Here, I have added a cast, this might fail on runtime. It can be easy to ignore... We can be a bit more expressive:

IPropertyThatIsAnInterface IAnInterface.InterfaceProperty
{
    get => InterfaceImplmentationProperty;
    set
    {
        if (!(value is ClassThatImplementsIPropertyThatIsAnInterface valueAsSpecificType))
        {
            throw new ArgumentException($"{nameof(value)} is not {typeof(ClassThatImplementsIPropertyThatIsAnInterface)}", nameof(value));
        }

        InterfaceImplmentationProperty = valueAsSpecificType;
    }
}

Alright, we saw above that we would have to bend the contract of the interface to make it work. So... how about making the contract more flexible?

We start by making the interface generic:

interface IAnInterface<TPropertyThatIsAnInterface>
    where TPropertyThatIsAnInterface : IPropertyThatIsAnInterface
{
    TPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

Just following your naming style here.

Then we can specify the type when we implement it:

class ClassThatImplementsIAnInterface : IAnInterface<ClassThatImplementsIPropertyThatIsAnInterface>
{
    public ClassThatImplementsIPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

If you really want the uglier name, you can do the explict implementation and delegated property thing.


To go further... what is the purpose of the interface? Isn't it so you can deal with the objects via the interface and not have to deal with specific types? - Ideally, you should not have to cast or check types.



From comment:

I have implemented your suggestion but am having an issue when trying to populate a List<IFileProcessors<IProcessorParameters>>. I get a "cannot implicitly convert type" error.

See? You want to treat them as the same type. Then make them the same type.

Well, there is a pattern for that, which is to have two version of the interface, one generic and that isn't. Then the generic interface inherits from the non-generic. I'd say, if you can avoid that, avoid it. If anything, it will lead to more type checks in runtime.


Ideally you should be able to deal with the type via its interface. The interface should be enough. So that the specific type is not required, and thus neither a cast to use it.

As I was explaining above the setter is a problem.

If the actual type of the property is more specific than the one on the interface, then the interface says that the property allows types that the class does not.

Can you remove the setter?

interface IAnInterface
{
    IPropertyThatIsAnInterface InterfaceProperty { get; }
}

interface IPropertyThatIsAnInterface
{
    int X { get; set; }
}

class ClassThatImplementsIAnInterface : IAnInterface
{
    public IPropertyThatIsAnInterface InterfaceProperty { get; }
}

ClassThatImplementsIAnInterface will still be able to initialize InterfaceProperty with any type that implements IPropertyThatIsAnInterface. The consumer would not have to be aware of it. And, asuming IPropertyThatIsAnInterface is useful as it is (it should) there should be no need to cast it to use it. In hinges on whatever or not the consumer can deal with the interface. At the moment when the consumer needs a particular type, you will be casting.

Zymotic answered 28/11, 2019 at 1:34 Comment(3)
That sounds pretty cool. I saw the comment above saying that I could use generics but wasn't sure how. Thanks for providing the example. I feel like this is more like what I want to achieve and it is more correct. The purpose of the interface is to create multiple different types of "FileProcessors" which all have different Parameters as they are different types of processors. I guess I thought that I could say that if the interface contained an interface property (Parameters), then I could put any class that implements the ProcessorParameters in there and satisfy the original contract.Bolin
I have implemented your suggestion but am having an issue when trying to populate a List<IFileProcessors<IProcessorParameters>>. I get a "cannot implicitly convert type" error. When casting the instantiation (IFileProcessors<IProcessorParameters>) new ExampleFileProcessor(args), the error goes away but when executed at runtime, I get an invalid cast exception saying that it can't cast ExampleFileProcessor to IFileProcessor. I would have thought i'd be able to do this without needing to cast (which you elude to in your answer).Bolin
@GeorgePippos I have no idea how ExampleFileProcessor is defined in that example. Expanded the answer.Zymotic
P
0

First, the property name must be InterfaceProperty, not InterfaceImplmentationProperty - the property names have to match.

Second, yes, the types have to match exactly. However, that does not stop you from assigning a ClassThatImplementsIPropertyThatIsAnInterface object to InterfaceProperty.

However, if you're saying that you have to cast before you use it, then that means that the interface isn't appropriate for what you're doing. The whole point of an interface is that consumers of your object can use the features of the interface without having to know how they're implemented.

The correct terminology here is that you "implement" the interface. Using an interface is a "can do" type relationship. For example, a class that implements IDisposable "can be disposed". A class can implement several interfaces, and that is commonly done. For example, SqlConnection implements both ICloneable and IDisposable, meaning that it "can be cloned" and "can be disposed".

However, a class can only "inherit" one other class (either abstract or concrete). This is a "is a" relationship. For example, SqlConnection "is a DbConnection", as you see in the declaration:

public sealed class SqlConnection : System.Data.Common.DbConnection, ICloneable, IDisposable

There is some more good reading about this here: Interfaces (C# Programming Guide)

And here: Abstract and Sealed Classes and Class Members (C# Programming Guide)

And here: Classes (C# Programming Guide) - Class inheritance

Here's a weird example I just made up:

interface ICanMakeNoise {
    void MakeNoise();
}

abstract class Animal {
    public abstract int NumberOfLegs { get; }
}

class Dog : Animal, ICanMakeNoise {

    public override int NumberOfLegs { get; } = 4;

    public void MakeNoise() {
        // Bark
    }
}

class Radio : ICanMakeNoise {
    public void MakeNoise() {
        // Play music
    }
}

Then I could have a method like this:

public void MakeThisMakeNoise(ICanMakeNoise noisyThing) {
    // This could be a dog or a radio, I don't have to know
    noisyThing.MakeNoise();
}
Playa answered 28/11, 2019 at 1:33 Comment(2)
Thanks for the reply and useful info! Reading your reply, it prompted me to try something: cast the getter for the interface property public IProcessorParameters Parameters { get { return (ProcessorParameters)Parameters; } set { } }. I know this is another work around and technically not correct though. I think what I really need to use is a template.Bolin
Woops, casting the return on the getter doesn't work so I am completely wrong there anyway.Bolin

© 2022 - 2024 — McMap. All rights reserved.