C
C#2y ago
Matt

❔ abstract class virtual methods vs default implementation interface

Okay so I’m a bit shaky on this but the way I understand it: An abstract class tells the code that the implementation of the class is incomplete. Therefore, another class must inherit the abstract base class. By using virtual methods within the base class, these methods can be either overridden or implemented directly. These virtual methods are therefore allowed to have a method body. If an override is not defined, the derived class simply inherits the virtual method’s base class definition. An interface provides a contract between the base and derived class that all methods must have an implementation. In C#8, an interface method is allowed a method body. If the method is not defined in the derived class, the default implementation is used. So what really is the difference between the two? Are my definitions incorrect? and are fields easier to use in abstract classes than interfaces?
9 Replies
Trinitek
Trinitek2y ago
default interface implementations were added to solve a specific problem, more specifically around API versioning in libraries they also have a particular quirk that, if you have a class that does not explicitly implement the default method, you have to cast it to the interface type to call it i.e. ((IMyInterface)existingType).NewMethod()
ero
ero2y ago
I feel like you're also missing abstract methods? Or did you deliberately not mention those Because virtual methods don't necessarily have anything to do with abstract classes
Matt
Matt2y ago
I didn’t mention abstract methods because they don’t equate in the comparison. Virtual methods within an abstract class (at least to me) seem to do exactly the same thing as default implementation in an interface. So in general, unless I’m looking to solve the specific problem default implementation was designed to solve, an abstract class with virtual methods is probably going to be better and easier to work with?
ero
ero2y ago
It's just incomparable really
Trinitek
Trinitek2y ago
well, what's "better" or "easier" is going to depend on your inheritance graph... using abstract classes comes with its own difficulties but generally, don't use default interface impls unless you have a good reason to do so
Matt
Matt2y ago
Thank you. Got it. I’ll give this some more thought and research before I commit to anything
Trinitek
Trinitek2y ago
here's 2 reasons why you might want to use default interface impls: https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/default-interface-methods-versions https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/mixins-with-default-interface-methods for the "update interfaces" example, this is an alternative to providing 2 separate interfaces for your consumers to implement: one being the original, and the second being the feature you added in an update this can also simplify your code when consuming the implementations because you won't have to do type checks for 2 interfaces
Hulkstance
Hulkstance2y ago
People in the C# world tend to prefer composition over inheritance i.e. they prefer to use interfaces over abstract classes Interfaces itself are meant to make the code testable
// Not testable because you can't mock the UserRepository using any DI framework
public Ctor()
{
_userService = new UserService(new UserRepository());
}
// Not testable because you can't mock the UserRepository using any DI framework
public Ctor()
{
_userService = new UserService(new UserRepository());
}
you could leverage the dependency inversion principle and invert the control, meaning your high level components should depend on abstractions, and not implementations.
// This is testable
public Ctor(UserRepository userRepository)
{
_userService = new UserService(userRepository);
}
// This is testable
public Ctor(UserRepository userRepository)
{
_userService = new UserService(userRepository);
}
that way u could mock it using NSubstitute or Moq
private readonly UserService _sut;
private readonly IUserRepository _userRepository = Substitute.For<IUserRepository>();

public UserServiceTests()
{
_sut = new UserService(userRepository);
}
private readonly UserService _sut;
private readonly IUserRepository _userRepository = Substitute.For<IUserRepository>();

public UserServiceTests()
{
_sut = new UserService(userRepository);
}
and test it easily the one below violates the "Closed for modification" principle from the SOLID because that switch statement will eventually make u modify that method when u gotta add more options
public class InvoiceService
{
public async Task Sign(int orderId)
{
var order = await _order.GetAsync(orderId);
var invoiceId = order.InvoiceId.GetValueOrDefault();
var invoice = await GetInvoiceByType(order, invoiceId);

switch (order.Type)
{
case OrderType.Company:
//... sign company invoice
break;
case OrderType.Personal:
//... sign personal invoice
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
public class InvoiceService
{
public async Task Sign(int orderId)
{
var order = await _order.GetAsync(orderId);
var invoiceId = order.InvoiceId.GetValueOrDefault();
var invoice = await GetInvoiceByType(order, invoiceId);

switch (order.Type)
{
case OrderType.Company:
//... sign company invoice
break;
case OrderType.Personal:
//... sign personal invoice
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
so u do
public interface IInvoice { ... }

public class PersonalInvoice : IInvoice { ... }

public class CompanyInvoice : IInvoice { ... }
public interface IInvoice { ... }

public class PersonalInvoice : IInvoice { ... }

public class CompanyInvoice : IInvoice { ... }
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.