❔ abstract class virtual methods vs default implementation interface

Mmcacutt11/9/2022
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?
TTrinitek11/9/2022
default interface implementations were added to solve a specific problem, more specifically around API versioning in libraries
TTrinitek11/9/2022
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
TTrinitek11/9/2022
i.e. ((IMyInterface)existingType).NewMethod()
EEro11/9/2022
I feel like you're also missing abstract methods? Or did you deliberately not mention those
EEro11/9/2022
Because virtual methods don't necessarily have anything to do with abstract classes
Mmcacutt11/9/2022
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.
Mmcacutt11/9/2022
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?
EEro11/9/2022
It's just incomparable really
TTrinitek11/9/2022
well, what's "better" or "easier" is going to depend on your inheritance graph... using abstract classes comes with its own difficulties
TTrinitek11/9/2022
but generally, don't use default interface impls unless you have a good reason to do so
Mmcacutt11/9/2022
Thank you. Got it. I’ll give this some more thought and research before I commit to anything
TTrinitek11/9/2022
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
TTrinitek11/9/2022
this can also simplify your code when consuming the implementations because you won't have to do type checks for 2 interfaces
EElectron11/9/2022
People in the C# world tend to prefer composition over inheritance i.e. they prefer to use interfaces over abstract classes
EElectron11/9/2022
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());
}

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);
}
EElectron11/9/2022
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);
}
EElectron11/9/2022
and test it easily
EElectron11/9/2022
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();
      }
   }
}
EElectron11/9/2022
so u do
public interface IInvoice { ... }

public class PersonalInvoice : IInvoice { ... }

public class CompanyInvoice : IInvoice { ... }
AAccord11/22/2022
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.