Free interfaces and inheritance in C #

I will show the problem with an example. There is a base class with a smooth interface:

class FluentPerson { private string _FirstName = String.Empty; private string _LastName = String.Empty; public FluentPerson WithFirstName(string firstName) { _FirstName = firstName; return this; } public FluentPerson WithLastName(string lastName) { _LastName = lastName; return this; } public override string ToString() { return String.Format("First name: {0} last name: {1}", _FirstName, _LastName); } } 

and child class:

 class FluentCustomer : FluentPerson { private long _Id; private string _AccountNumber = String.Empty; public FluentCustomer WithAccountNumber(string accountNumber) { _AccountNumber = accountNumber; return this; } public FluentCustomer WithId(long id) { _Id = id; return this; } public override string ToString() { return base.ToString() + String.Format(" account number: {0} id: {1}", _AccountNumber, _Id); } } 

The problem is that when calling customer.WithAccountNumber("000").WithFirstName("John").WithLastName("Smith") you cannot add .WithId(123) at the end, because the return type of the WithLastName() method is a FluentPerson (not a FluentCustomer).

How is this problem usually resolved?

+55
inheritance c # fluent-interface
Feb 17 '10 at 6:36
source share
7 answers

You can use generics to achieve this.

 public class FluentPerson<T> where T : FluentPerson<T> { public T WithFirstName(string firstName) { // ... return (T)this; } public T WithLastName(string lastName) { // ... return (T)this; } } public class FluentCustomer : FluentPerson<FluentCustomer> { public FluentCustomer WithAccountNumber(string accountNumber) { // ... return this; } } 

And now:

 var customer = new FluentCustomer() .WithAccountNumber("123") .WithFirstName("Abc") .WithLastName("Def") .ToString(); 
+40
Feb 17 '10 at 7:16
source share

Try some extension methods.

 static class FluentManager { public static T WithFirstName<T>(this T person, string firstName) where T : FluentPerson { person.FirstName = firstName; return person; } public static T WithId<T>(this T customer, long id) where T : FluentCustomer { customer.ID = id; return customer; } } class FluentPerson { public string FirstName { private get; set; } public string LastName { private get; set; } public override string ToString() { return string.Format("First name: {0} last name: {1}", FirstName, LastName); } } class FluentCustomer : FluentPerson { public long ID { private get; set; } public long AccountNumber { private get; set; } public override string ToString() { return base.ToString() + string.Format(" account number: {0} id: {1}", AccountNumber, ID); } } 

after you can use both

 new FluentCustomer().WithId(22).WithFirstName("dfd").WithId(32); 
+40
Feb 17 '10 at 7:39
source share

Logically, you need to configure the material from the most specific (client) to the least specific (person), otherwise it is even difficult to read, despite the free interface. Following this rule, in most cases you do not need to worry. If for any reason you still need to mix them, you can use intermediate underscores, for example

 static class Customers { public static Customer AsCustomer(this Person person) { return (Customer)person; } } customer.WIthLastName("Bob").AsCustomer().WithId(10); 
+4
Feb 17 '10 at 6:47 a.m.
source share

A solution in which you need a free interface, inheritance, as well as some generics ...

In any case, as I said: this is the only option if you want to use inheritance and access also to protected members ...

 public class GridEx<TC, T> where TC : GridEx<TC, T> { public TC Build(T type) { return (TC) this; } } public class GridExEx : GridEx<GridExEx, int> { } class Program { static void Main(string[] args) { new GridExEx().Build(1); } } 
+4
Oct. 15 '15 at 5:32
source share
  public class FluentPerson { private string _FirstName = String.Empty; private string _LastName = String.Empty; public FluentPerson WithFirstName(string firstName) { _FirstName = firstName; return this; } public FluentPerson WithLastName(string lastName) { _LastName = lastName; return this; } public override string ToString() { return String.Format("First name: {0} last name: {1}", _FirstName, _LastName); } } public class FluentCustomer { private string _AccountNumber = String.Empty; private string _id = String.Empty; FluentPerson objPers=new FluentPerson(); public FluentCustomer WithAccountNumber(string accountNumber) { _AccountNumber = accountNumber; return this; } public FluentCustomer WithId(string id) { _id = id; return this; } public FluentCustomer WithFirstName(string firstName) { objPers.WithFirstName(firstName); return this; } public FluentCustomer WithLastName(string lastName) { objPers.WithLastName(lastName); return this; } public override string ToString() { return objPers.ToString() + String.Format(" account number: {0}", _AccountNumber); } } 

And called with

  var ss = new FluentCustomer().WithAccountNumber("111").WithFirstName("ram").WithLastName("v").WithId("444").ToString(); 
+3
Feb 17 '10 at 6:48
source share

Is a runaway interface really the best call here or is a better initializer?

  var p = new Person{ LastName = "Smith", FirstName = "John" }; var c = new Customer{ LastName = "Smith", FirstName = "John", AccountNumber = "000", ID = "123" }; 

Unlike a free interface, this works fine without inherited methods, returning the base class and ruining the chain. When you inherit a property, it doesn't really matter to the caller whether FirstName first implemented in Person or Customer or Object.

I believe this is more readable, whether on one or more lines, and you don’t have to worry about providing impeccable self-locking features that match each property.

+3
Feb 17 '10 at 7:10
source share

I know this is an old question, but I wanted to share my thoughts with you about it.

What about sharing fluency, which is a kind of mechanism, and your classes when you can? This will leave your lessons clean.

How about this?

Classes

  public class Person { public string FirstName { get; set; } public string LastName {get; set;} public override string ToString() { return $"First name: {FirstName} last name: {LastName}"; } } public class Customer : Person { public string AccountNumber { get; set; } public long Id { get; set; } public override string ToString() { return base.ToString() + $" account number: {AccountNumber} id: {Id}"); } } 

A class that adds a free mechanism

  public class FluentCustomer { private Customer Customer { get; } public FluentCustomer() : this(new Customer()) { } private FluentCustomer(Customer customer) { Customer = customer; } public FluentCustomer WithAccountNumber(string accountNumber) { Customer.AccountNumber = accountNumber; return this; } public FluentCustomer WithId(long id) { Customer.Id = id; return this; } public FluentCustomer WithFirstName(string firstName) { Customer.FirstName = firstName; return this; } public FluentCustomer WithLastName(string lastName) { Customer.LastName = lastName; return this; } public static implicit operator Customer(FluentCustomer fc) { return fc.Customer; } public static implicit operator FluentCustomer(Customer customer) { return new FluentCustomer(customer); } } 

Extension method to switch to free mode

  public static class CustomerExtensions { public static FluentCustomer Fluent(this Customer customer) { return customer; } } 

The same example as in the question

 Customer customer = new Customer().Fluent() .WithAccountNumber("000") .WithFirstName("John") .WithLastName("Smith") .WithId(123); 
+1
Jan 10 '19 at 12:49
source share



All Articles