C
C#10mo ago
TYoemtais.z

Polymorphism in C# makes no sense

Let's assume such a case:
public interface ICar
{
string Name { get; }
IEngine Engine { get; }
}

public interface IEngine
{
string Name { get; }
}

public class Engine: IEngine
{
public string Name { get; }
public string EngineClassMethod()
{
return "Engine class";
}
}

public class Car : ICar
{
public string Name { get; }
public Engine Engine { get; }
}
public interface ICar
{
string Name { get; }
IEngine Engine { get; }
}

public interface IEngine
{
string Name { get; }
}

public class Engine: IEngine
{
public string Name { get; }
public string EngineClassMethod()
{
return "Engine class";
}
}

public class Car : ICar
{
public string Name { get; }
public Engine Engine { get; }
}
And for the Car class I have an error
Error CS0738 'Car' does not implement interface member 'ICar.Engine'. 'Car.Engine' cannot implement 'ICar.Engine' because it does not have the matching return type of 'IEngine'
Error CS0738 'Car' does not implement interface member 'ICar.Engine'. 'Car.Engine' cannot implement 'ICar.Engine' because it does not have the matching return type of 'IEngine'
I could use IEngine instead of Engine in Car, which would fix the error. But then I can't use the methods of the Engine class. I completely don't understand why I can't use Engine since this class implements IEngine. I can't have different classes implementing ICar and having inside different classes implementing IEngine, they all have to be on the base IEngine. This is terribly frustrating. Instead of using interfaces in such cases, I have to get rid of them.
15 Replies
V0FBU1VM
V0FBU1VM10mo ago
Engine property in the Car class is of type Engine, but the ICar interface expects a property of type IEngine. Even though Engine implements IEngine, they are not the same type.
TYoemtais.z
TYoemtais.zOP10mo ago
I know, I understand the error. But it just doesn't make sense to me. Since Engine implements IEngine, it completely meets its requirements. Wouldn't it be much more useful if it were truly polymorphic? I do not understand why in this case it was done this way.
V0FBU1VM
V0FBU1VM10mo ago
If ICar was allowed to have a property of type Engine, then every car would need to have an Engine, not just an IEngine. This would limit the flexibility of your code. For example, you wouldn’t be able to have a Car with a SuperEngine (a different class that implements IEngine). Basically by having a property of type IEngine in ICar. You aren't enforcing any specific type of engine. Take this example:
public class Bugatti : ICar
{
public string Name { get; }
public IEngine Engine { get; } // here you can specify any engine you want, you are not enforcing a specific engine. (ShittyEngine) :)
}

public class Supra : ICar
{
public string Name { get; }
public IEngine Engine { get; } // (SuperEngine)
}
public class Bugatti : ICar
{
public string Name { get; }
public IEngine Engine { get; } // here you can specify any engine you want, you are not enforcing a specific engine. (ShittyEngine) :)
}

public class Supra : ICar
{
public string Name { get; }
public IEngine Engine { get; } // (SuperEngine)
}
TYoemtais.z
TYoemtais.zOP10mo ago
I can specify any engine but then I cannot use methods of this different engines. Like bugatti.Engine.WorkOnShittyEngine(). I'm only limited to use things that IEngine provides. I thinks this is what limit the flexibility of mine code.
CoRoys
CoRoys10mo ago
This is a design choice in the language itself to ensure type safety It's mostly useful in larger codebases
cap5lut
cap5lut10mo ago
u can use two interfaces, and explicit interface implementations to get it to work how u want:
public interface IEngine;

public interface ICar
{
public IEngine Engine { get; }
}

public interface ICar<TEngine> where TEngine : IEngine
{
public TEngine Engine { get; }
}

public class Engine : IEngine;

public class Car : ICar, ICar<Engine>
{
IEngine ICar.Engine => Engine;
public Engine Engine { get; }
}
public interface IEngine;

public interface ICar
{
public IEngine Engine { get; }
}

public interface ICar<TEngine> where TEngine : IEngine
{
public TEngine Engine { get; }
}

public class Engine : IEngine;

public class Car : ICar, ICar<Engine>
{
IEngine ICar.Engine => Engine;
public Engine Engine { get; }
}
that way only if u use it as ICar (the not-generic version), u get the IEngine Engine { get; } property on the API surface, else u get the Engine Engine { get; } property
Joschi
Joschi10mo ago
You could do this
public class SuperEngine: IEngine
{
public string Name { get; }
}

public class Car : ICar
{
public string Name { get; }
public IEngine Engine => _engine;
private SuperEngine _engine;
}
public class SuperEngine: IEngine
{
public string Name { get; }
}

public class Car : ICar
{
public string Name { get; }
public IEngine Engine => _engine;
private SuperEngine _engine;
}
Then you can use your specific engine inside your class. And the public facing side is defined by your interface. That's also a good solution
cap5lut
cap5lut10mo ago
i first thought of ur solution as well, but that wouldnt work for the brought up example (bugatti.Engine.WorkOnShittyEngine()) either
Joschi
Joschi10mo ago
Fair
cap5lut
cap5lut10mo ago
ICar<TEngine> could also extend ICar, so that Car only has to have ICar<Engine> on its base type list
TYoemtais.z
TYoemtais.zOP10mo ago
The solution is good, although it causes a large code add-on. Apparently I am used to more flexible languages 😛 Anyway, thank you
canton7
canton710mo ago
The problem is this:
public interface ICar
{
string Name { get; }
IEngine Engine { get; set; }
}

public interface IEngine
{
string Name { get; }
}

public class Car : ICar
{
public string Name { get; }
public Engine Engine { get; set; }
}
public interface ICar
{
string Name { get; }
IEngine Engine { get; set; }
}

public interface IEngine
{
string Name { get; }
}

public class Car : ICar
{
public string Name { get; }
public Engine Engine { get; set; }
}
Now you can do:
class ShittyEngine : IEngine { ... }

ICar car = new Car();
car.Engine = new ShittyEngine();
class ShittyEngine : IEngine { ... }

ICar car = new Car();
car.Engine = new ShittyEngine();
Oops! Even though your Car class has an engine of type Engine, I managed to assign a ShittyEngine instance to it. That's a big problem if your Engine class has a Start() method on it, which isn't available on IEngine/ShittyEngine. If Car tries to call this.Engine.Start(), and I've gone and replaced the engine with a ShittyEngine, what happens? And the normal workaround is with explicit interface implementation:
public class Car : ICar
{
public Engine Engine { get; }
ICar.IEngine Engine => Engine;
}
public class Car : ICar
{
public Engine Engine { get; }
ICar.IEngine Engine => Engine;
}
cap5lut
cap5lut10mo ago
i guess the whole ICar<TEngine> wasnt even needed 😂
V0FBU1VM
V0FBU1VM10mo ago
Just get a real car with a proper engine. Don't make a virtual one. harold
reflectronic
reflectronic10mo ago
this is only supported for things defined on abstract classes, not interfaces it is not supported for interfaces because it is a lot of work, and nobody really is interested, and the workaround with explicit implementation works fine another thing you can do is use a default interface member
public interface IEngine { }
public interface ICar
{
IEngine Engine { get; }
}
public interface ICar<TEngine> : ICar
where T : IEngine
{
new TEngine Engine { get; }
IEngine ICar.Engine => Engine;
}
public interface IEngine { }
public interface ICar
{
IEngine Engine { get; }
}
public interface ICar<TEngine> : ICar
where T : IEngine
{
new TEngine Engine { get; }
IEngine ICar.Engine => Engine;
}
this removes all of the boilerplate from the implementing classes
Want results from more Discord servers?
Add your server