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 IPropertyThatIsAnInterface
s 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?
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.