I have a project with a list that can be List<Fuel>
, List<Trip>
, or List<Purchase>
. The models are very similar; the key difference is in how they are processed. Is there a way I can create one list object that can contain all three and be able to send the types to the right methods?
CodePudding user response:
There are multiple ways you can achieve that:
OPTION 1: List<object>
The easies one is List<object>
and switch the type. IN c# you can do this:
var mylist = new List<object>(){ new Fuel(), new Trip(), new Purchase() };
foreach (var item in mylist)
{
switch (item)
{
case Fuel item_as_fuel:
item_as_fuel.Refuel(6);
break;
case Trip item_as_trip:
item_as_trip.ToCity("Berlin");
break;
case Purchase pp:
pp.SendPayment();
break;
default:
throw new IvalidOperationException("invalid type");
}
}
OPTION 2: interface
Another easy option is to implement an Interface Contrary to the abstract class in the previus answer, the interface will not beak your inherithance and will allow for easy expansion.
interface ISomeMeaningfulNameThatCorrelatesTheThreeClasses
{
void PerformOrAnotherMeaningfulNameForYourSpecificCase(params object[] args);
}
// i will now call it IBase to simplify but you get the idea
class Fuel : IBase
{
// i strongly suggest explicit implementations
// because it will only apply when used as interface
void IBase.Perform(params object[] args)
{
//custom implementation
this.Refuel((int)args[0]);
}
}
Now, this is appropriate use of inheritance because interfaces are contracts, and this particolar explicit implementation of the contract ensures it's used only when explicitly casted to interface. ie:
IBase fuel = new Fuel();
fuel.Perform(6); // works
Fuel fuel = new Fuel();
fuel.Perform(6); // won't compile, Perform doesn't exist in Fuel
Option 3: union type
You could create a simple union type, having 3 nullable references to your types. This also could work as a substitute to the interface option, performing the correct operation in a method
public readonly struct FuelTripPurchaseUnion
{
readonly Fuel? _f; //null
readonly Trip? _t; //null
readonly Purchase? _p; //null
public FuelTripPurchaseUnion (object original)
{/* assign to the correct variable */}
public FuelTripPurchaseUnion (Fuel fuel = default, Trip trip = default, Purchase purchase = default)
{/* assign to the correct variable */
// used like FuelTripPurchaseUnion(fuel: thefuel);
}
public void Perform()
{
// you can put your logic here and call the correct one...
}
public object Value()
{
// ...or you can return the correct value
}
}
Obviusly you could optimize the code further with advanced type annotations as said in the comments but it's more advanced stuff (new to me too, that's why i'm not putting it in the answer)
Option 3.1: external library
As said in the previus comments, another option is to use an external library giving you something like a OneOf<A,B,C>
object instead of implementing one yourself.
but i won't suggest you use this one
CodePudding user response:
Quick-and-dirty tagged-union:
[StructLayout( LayoutKind.Explicit )]
public readonly struct Fuel_or_Trip_or_Purchase : IEquatable<Fuel_or_Trip_or_Purchase>, IEquatable<Fuel>, IEquatable<Trip>, IEquatable<Purchase>
{
private const Byte TAG_DEFAULT = 0;
private const Byte TAG_FUEL = 1;
private const Byte TAG_TRIP = 2;
private const Byte TAG_PURCHASE = 3;
public static implicit operator Fuel_or_Trip_or_Purchase( Fuel fuel ) => new Fuel_or_Trip_or_Purchase( fuel );
public static implicit operator Fuel_or_Trip_or_Purchase( Trip trip ) => new Fuel_or_Trip_or_Purchase( trip );
public static implicit operator Fuel_or_Trip_or_Purchase( Purchase purchase ) => new Fuel_or_Trip_or_Purchase( purchase );
[FieldOffset(0)] private readonly Byte tag;
[FieldOffset(1)] private readonly Object obj;
[FieldOffset(1)] private readonly Fuel? fuel;
[FieldOffset(1)] private readonly Trip? trip;
[FieldOffset(1)] private readonly Purchase? purchase;
public Fuel_or_Trip_or_Purchase( Fuel fuel )
{
this.obj = fuel;
this.purchase = null;
this.trip = null;
this.fuel = fuel ?? throw new ArgumentNullException(nameof(fuel));
this.tag = TAG_FUEL;
}
public Fuel_or_Trip_or_Purchase( Trip trip )
{
this.obj = trip;
this.purchase = null;
this.fuel = null;
this.trip = trip ?? throw new ArgumentNullException(nameof(trip));
this.tag = TAG_TRIP;
}
public Fuel_or_Trip_or_Purchase( Purchase purchase )
{
this.obj = purchase;
this.fuel = null;
this.trip = null;
this.purchase = purchase ?? throw new ArgumentNullException(nameof(purchase));
this.tag = TAG_PURCHASE;
}
public Boolean TryGetFuel( [NotNullWhen(true)] out Fuel? fuel )
{
fuel = ( this.tag == TAG_FUEL ) ? this.fuel : null;
return fuel != null;
}
public Boolean TryGetTrip( [NotNullWhen(true)] out Trip? trip )
{
trip = ( this.tag == TAG_TRIP ) ? this.trip : null;
return trip != null;
}
public Boolean TryGetPurchase( [NotNullWhen(true)] out Purchase? purchase )
{
purchase = ( this.tag == TAG_PURCHASE ) ? this.purchase : null;
return purchase != null;
}
public override Int32 GetHashCode() => HashCode.Combine( this.tag, this.obj.GetHashCode() );
public override Boolean Equals( Object? obj )
{
if( obj is null )
{
return false;// this.tag == TAG_DEFAULT;
}
else if( obj is Fuel_or_Trip_or_Purchase other )
{
return this.Equals( other: other );
}
else if( obj is Fuel f )
{
return this.Equals( f: f );
}
else if( obj is Trip t )
{
return this.Equals( t: t );
}
else if( obj is Purchase p )
{
return this.Equals( p: p );
}
else
{
return false;
}
}
public Boolean Equals( Fuel_or_Trip_or_Purchase other ) => ( this.tag == other.tag ) && ( Object.ReferenceEquals( this.obj, other.obj ) );
public Boolean Equals( Fuel? f ) => ( this.tag == TAG_FUEL ) && ( Object.ReferenceEquals( this.fuel , f ) );
public Boolean Equals( Trip? t ) => ( this.tag == TAG_TRIP ) && ( Object.ReferenceEquals( this.trip , t ) );
public Boolean Equals( Purchase? p ) => ( this.tag == TAG_PURCHASE ) && ( Object.ReferenceEquals( this.purchase, p ) );
public TResult Match<TResult>(
Func<Fuel,TResult> whenFuel,
Func<Trip,TResult> whenTrip,
Func<Purchase,TResult> whenPurchase,
)
{
switch( this.tag )
{
case TAG_FUEL : return whenFuel ( this.fuel! );
case TAG_TRIP : return whenTrip ( this.trip! );
case TAG_PURCHASE: return whenPurchase( this.purchase! );
default:
throw new InvalidOperationException( "default(Fuel_or_Trip_or_Purchase)" );
}
}
// Method forwarding:
// Assuming Fuel, Trip and Purchase all have these members:
public Decimal Cost => this.Match( f => f.Cost, t => t.Cost, p => p.Cost );
public String Name => this.Match( f => f.Name, t => t.Name, p => p.Name );
// etc...
}
Used like so:
Fuel fuel0 = ..., fuel1 = ..., fuel2 = ...;
Trio trip0 = ..., trip1 = ..., trip2 = ...;
Purchase p = ...
List<Fuel_or_Trip_or_Purchase> list = new List<Fuel_or_Trip_or_Purchase>();
list.Add( fuel0 );
list.Add( trip0 );
list.Add( p );
list.Add( fuel1 );
list.Add( trip1 );
Decimal totalCost = list.Select( e => e.Cost ).Sum();
String allNames = String.Join( e => "\"" e.Name "\"", separator: "\r\n" );
// Or:
Decimal totalCost = 0M;
StringBuilder allNamesSB = new StringBuilder();
foreach( var e in list )
{
totalCost = e.Cost;
_ = allNamesSB.Append( '"' ).Append( e.Name ).Append( '"' ).AppendLine();
}
String allNames = allNamesSB.ToString();
CodePudding user response:
What you want is implemented through base class
public abstract class AbstractProcessor
{
public void CommonMethod() {}
public abstract void SpecificMethod() ;
}
public class Fuel : AbstractProcessor
{
public override void SpecificMethod ()
{
// do fuel processing
}
}