I wanted to ask about a scenario that happened to me. I am working with different models that implement a common generic property, which has different implementations, however, this generic property is common.
In this case, I am doing a Mapping where they have a generic property T in common with other models, in the example I created it is Comparable and NonComparable, both implement a Unit T.
Initially, I have a Mapper for comparable and another for IComparable, as you can see:
Code Link: https://github.com/xeof-landmark/generic-tests
Branch: main
Repeating Code Here:
Program
class Program
{
static Fixture fixture = new Fixture();
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var map = new Map();
var unitsComparables = InitializeComparables();
var unitsNonComparables = InitializeNonComparables();
var unitsMappedCompared = map.MapList(unitsComparables);
var unitsMappedNonCompared = map.MapList(unitsNonComparables);
var units = new List<IUnit>();
units.AddRange(unitsMappedCompared);
units.AddRange(unitsMappedNonCompared);
foreach (var unit in units)
{
Console.WriteLine(unit.Name);
Console.WriteLine(Environment.NewLine);
}
}
static List<Comparable<IUnit>> InitializeComparables()
{
return new List<Comparable<IUnit>>()
{
new Comparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new Comparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new Comparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new Comparable<IUnit> { Unit = fixture.Create<SaleUnit>() },
new Comparable<IUnit> { Unit = fixture.Create<SaleUnit>() },
};
}
static List<NonComparable<IUnit>> InitializeNonComparables()
{
return new List<NonComparable<IUnit>>()
{
new NonComparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new NonComparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new NonComparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new NonComparable<IUnit> { Unit = fixture.Create<SaleUnit>() },
new NonComparable<IUnit> { Unit = fixture.Create<SaleUnit>() },
};
}
}
Classes:
public class SaleUnit : IUnit
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class LegacyUnit : IUnit
{
public int Id { get; set; }
private string name;
public string Name
{
get { return $"{name}.Legacy"; }
set { name = value; }
}
public string Description { get; set; }
}
public interface IUnit
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class Comparable<T>
where T : IUnit
{
public T Unit { get; set; }
public int ExisingUnit { get; set; }
}
public class NonComparable<T>
where T : IUnit
{
public T Unit { get; set; }
public bool IsDifferent(int id) => true;
public void AddNewPrices(decimal prices) => throw new System.NotImplementedException();
}
Mapper
public class Map
{
static Fixture fixture = new Fixture();
public List<IUnit> MapList(List<Comparable<IUnit>> compareOnes)
{
return new List<IUnit>()
{
fixture.Create<LegacyUnit>(),
fixture.Create<LegacyUnit>(),
fixture.Create<LegacyUnit>(),
fixture.Create<SaleUnit>(),
fixture.Create<SaleUnit>(),
};
}
public List<IUnit> MapList(List<NonComparable<IUnit>> compareOnes)
{
return new List<IUnit>()
{
fixture.Create<LegacyUnit>(),
fixture.Create<LegacyUnit>(),
fixture.Create<LegacyUnit>(),
fixture.Create<SaleUnit>(),
fixture.Create<SaleUnit>(),
};
}
}
I realized that at first, I was repeating a lot of code, in turn, I was reading a bit about covariance (I tried to do it but I couldn't).
I ended up solving it by casting them as an IComparable interface and then using a common Mapper for the IComparables.
Resolution Link: https://github.com/xeof-landmark/generic-tests/pull/1
So I ended up doing this
First I've created a new Interface to abstract the generic Unit.
public interface IComparable<T>
where T : IUnit
{
public T Unit { get; set; }
}
I have inherited the Comparable and NonComparable from IComparable
public class Comparable<T> : IComparable<T>
where T : IUnit
{
public T Unit { get; set; }
public int ExisingUnit { get; set; }
}
public class NonComparable<T> : IComparable<T>
where T : IUnit
{
public T Unit { get; set; }
public bool IsDifferent(int id) => true;
public void AddNewPrices(decimal prices) => throw new System.NotImplementedException();
}
Then I added a new method overload to my Map Class to handle both Comparable and NonComparable MapList(List<IComparable> unitsComparables) to be able to handle both maps.
public List<IUnit> MapList(List<IComparable<IUnit>> unitsComparables)
{
var mapUnitComparables = unitsComparables.Where(x => x is Comparable<IUnit>).Select(s => (Comparable<IUnit>)s).ToList();
var mapUnitNonComparables = unitsComparables.Where(x => x is NonComparable<IUnit>).Select(s => (NonComparable<IUnit>)s).ToList();
var result = new List<IUnit>();
result.AddRange(MapList(mapUnitComparables));
result.AddRange(MapList(mapUnitNonComparables));
return result;
}
And Finally my Program:
class Program
{
static Fixture fixture = new Fixture();
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var map = new Map();
var unitsComparables = InitializeComparables();
var units = map.MapList(unitsComparables);
foreach (var unit in units)
{
Console.WriteLine(unit.Name);
Console.WriteLine(Environment.NewLine);
}
}
static List<IComparable<IUnit>> InitializeComparables()
{
return new List<IComparable<IUnit>>()
{
new Comparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new Comparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new Comparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new Comparable<IUnit> { Unit = fixture.Create<SaleUnit>() },
new Comparable<IUnit> { Unit = fixture.Create<SaleUnit>() },
new NonComparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new NonComparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new NonComparable<IUnit> { Unit = fixture.Create<LegacyUnit>() },
new NonComparable<IUnit> { Unit = fixture.Create<SaleUnit>() },
new NonComparable<IUnit> { Unit = fixture.Create<SaleUnit>() },
};
}
}
Now my question is, this solves it, but ..... Would it be the best way to solve it? or can it be done better?
Could I use covariance on this occasion? is it well planned?