Reputation: 13
I'm a bit new to C# interfaces, and have run into a rather awkward solution to an extension method on inherited interface lists problem. Example interfaces look like this:
public interface IData
{
int Value { get; set; }
}
public interface IDataWithName : IData
{
string Name { get; set; }
}
public interface IDataContainer
{
IList<IData> DataList { get; set; }
}
public interface IDataWithNameContainer : IDataContainer
{
new IList<IDataWithName> DataList { get; set; }
}
With an extension method:
public static class ExtensionMethod
{
public static int CountNumberOfIDataItems(this IDataContainer i)
{
return i.DataList.Count();
}
}
When implementing these interfaces, both the IList DataList and IList IDataContainer.DataList need to be implemented. While this is possible, the resulting code is inelegant:
public class DataNameImplimentatioFixed : IDataWithNameContainer
{
public IList<IDataWithName> DataList { get; set; }
IList<IData> IDataContainer.DataList
{
get => new List<IData>(DataList);
set
{
DataList = new List<IDataWithName>();
foreach (IDataWithName _dataLoop in value) { DataList.Add(_dataLoop); }
}
}
}
Duplicating the list each time that the extension is called could lead to some performance problems, as well as needing an additional check that any further methods on IDataContainer won't try to add classes that inherit from IData but not IDataWithName.
It feels like there should be a better way of using the extension methods, that isn't as vulnerable to problems with future extensions. The best solution I can some up with is to take the IList DataList out of IDataContainer, and have a separate extension method for each class that contains a list.
Can anyone come up with a better solution than this?
Upvotes: 1
Views: 331
Reputation: 6749
You don't need IDataContainer
and IDataWithNameContainer
. You already have the values in the IDataWithName
interface so why is IDataWithNameContainer
also inheriting from IDataContainer
? Isn't that why you made the extra interface?
IData
IDataWithName : IData
IList<IDataWithName> //This already has a list of (name) and (value)
Simply make your class like so:
public interface IDataWithNameContainer // Do not add this here -> : IDataContainer
{
IList<IDataWithName> DataList { get; set; }
}
Upvotes: 0
Reputation: 131180
The problem is that IDataWithNameContainer
hides the DataList
property it inherits from IDataContainer
, not the extension method. It shouldn't do so.
The easy way to create a derived container class that only accepts a specific IData implementation is to make the container interfaces generic and use a stricter type constraint in IDataWithNameContainer
:
public interface IData
{
int Value { get; set; }
}
public interface IDataWithName : IData
{
string Name { get; set; }
}
public interface IDataContainer<T> where T:IData
{
IList<T> DataList { get; set; }
}
public interface IDataWithNameContainer<T> : IDataContainer<T> where T:IDataWithName
{
}
public static class ExtensionMethod
{
public static int CountNumberOfIDataItems<T>(this IDataContainer<T> i) where T:IData
{
return i.DataList.Count;
}
}
Creating a named container this way is easy :
public class Boo:IDataWithName
{
public int Value { get; set; }
public string Name { get; set; }
}
public class BooContainer: IDataWithNameContainer<Boo>
{
public IList<Boo> DataList { get; set; }
}
Update
There's no reason to add new items to the DataList
in a loop by the way. The List
constructor can accept an IEnumerable with the initial values:
public class BooContainer: IDataWithNameContainer<Boo>
{
IList<Boo> _list=new List<Boo>();
public IList<Boo> DataList
{
get => _list;
set => _list=new List<Boo>(value);
}
}
This allows storing both lists and arrays to DataList
by copying their contents into a new List.
This will throw if someone sets a null. To avoid this, one can use the null replacement operator in the setter :
set => _list=new List<Boo>(value ?? new Boo[0]);
Upvotes: 3