Contravariance in Generics C# - c#

I don't know who resolve this segment code with variance:
I have an abstract father class:
public abstract class PdfObject
{...}
And two child classes:
public class PdfText : PdfObject
{...}
public class PdfImage : PdfObject
{...}
Now, my wrong or empiric code is the next:
public IList<PdfText> GetTexts()
{
List<PdfText> result = new List<PdfText>();
List<PdfObject> list = GetList();
foreach(var item in list)
{
if(item is PdfText) result.Add(item)
}
return result;
}
public List<PdfObject> GetList()
{...}
Well, i read a lot of this theme, but don't stand how use variance in generics or use a better solution for this issue.
Please, help me and thanks.

This doesn't have much to do with variance, directly. Your problem is here:
public IList<PdfText> GetTexts()
{
List<PdfText> result = new List<PdfText>();
List<PdfObject> list = GetList();
foreach(var item in list)
{
if(item is PdfText) result.Add(item)
}
return result;
}
The static type of the item variable is PdfObject so you cannot add it to result; you need to cast it. For example
if (item is PdfText) result.Add((PdfText)item);
This is inefficient because you check the type twice: once for the is operator and once for the cast. Instead, you're supposed to do this:
public IList<PdfText> GetTexts()
{
List<PdfText> result = new List<PdfText>();
List<PdfObject> list = GetList();
foreach(var item in list)
{
var textItem = item as PdfText
if (textItem != null) result.Add(textItem)
}
return result;
}
Or, you can use linq:
var result = GetList().OfType<PdfText>().ToList();

You could do this...
public IList<PdfText> GetTexts()
{
List<PdfText> result = GetList()
.Where(x => x is PdfText)
.Select(x => (PdfText)x)
.ToList();
return result;
}
Edited: This works, but OfType is better.

You could have a better solution in this situation.
public class ClientOfPdfObject<T> where T: PdfObject
{
public List<T> GetItems()
{
List<PdfObject> list = GetList();
var result = new List<T>();
foreach (var pdfObject in list)
{
if (typeof (T) == pdfObject.GetType())
result.Add((T) pdfObject);
}
return result;
}
//Get PdfObjects somewhere (ex. Db)
private List<PdfObject> GetList()
{
var list = new List<PdfObject>
{
new PdfImage(),
new PdfImage(),
new PdfImage(),
new PdfText(),
new PdfText(),
new PdfText(),
new PdfText()
};
return list;
}
}
static void main()
{
var text = new ClientOfPdfObject<PdfText>();
//contains 4 itmes (PdfText)
var pdfTexts = text.GetItems();
var image = new ClientOfPdfObject<PdfImage>();
//contains 3 items (PdfImage)
var pdfImages = image.GetItems();
}
Tomorrow, when you add more pdf objects (ex. PdfGraph), you don't need to change anything.

Related

C# Arbitrarily Nested List Transformation with Lambda Expression

I am trying to do some transformations on lists of different classes. For example, there are class A and class B.
class A
{
private int Data;
public A(int InputData)
{
this.Data = InputData;
}
public int GetData()
{
return this.Data;
}
public B ToB()
{
return new B(this);
}
}
class B
{
private double Data;
public B(A ObjectA)
{
this.Data = Math.Cos(ObjectA.GetData()); // For example
}
public double GetData()
{
return this.Data;
}
}
Then, ListB1 is as the transformation result from ListA1.
List<A> ListA1 = new List<A>() { new A(0), new A(1), new A(2) };
List<B> ListB1 = new List<B>();
foreach (var EachItem in ListA1)
{
ListB1.Add(EachItem.ToB());
}
I am wondering that is there any way to use lambda expression to perform this example? Maybe something like List<B> ListB1 = ListA1.ConvertAll(ObjectA => { ObjectA.ToB(); }); However, it seems that the input parameter of ConvertAll method is designed as a Converter object, not lambda expression. Moreover, is there any easy way to deal with arbitrarily nested list cases, such as the following ListA2? I know that ListB2 could be generated by nested foreach loop, but I am looking forward to find a way like List<List<B>> ListB2 = ListA2.ConvertAll(ObjectA => { ObjectA.ToB(); });. Then the same structure could be applied to arbitrarily nested list.
List<List<A>> ListA2 = new List<List<A>>();
ListA2.Add(ListA1);
ListA2.Add(ListA1);
List<List<B>> ListB2 = new List<List<B>>();
foreach (var EachItem1 in ListA2)
{
List<B> temp = new List<B>();
foreach (var EachItem2 in EachItem1)
{
temp.Add(EachItem2.ToB());
}
ListB2.Add(temp);
}
Any suggestions are welcome.
You can Do Something like
ListA2.ForEach(x => x.ToB()).ToList();
That way you now have a list of B
You could use Linq's Select to translate each item;
List<B> ListB1 = ListA1.Select(a => a.ToB()).ToList();
List<List<B>> ListB2 = ListA2.Select(a2 => a2.Select(a => a.ToB()).ToList()).ToList();

How to add List<TestEntity> to List<T>?

I have a method MapResults which returns IEnumerable<T> and I have a list temp. I want to add returnVal and temp lists and return IEnumerable<T>.
public class RetrieverClass<T, TEntity> : IRetriever<T, TEntity>
where T : BaseFileEntity
where TEntity : class, IEntity<int>
{
public async Task<IEnumerable<T>> TestMethod(IEnumerable<Item> items)
{
var returnVal = MapResults(result).ToList();
List<TestEntity> temp = new List<TestEntity>();
foreach (var testNo in testNos)
{
TestEntity test = CreateTestEntity(testNo);
temp.Add(test);
}
returnVal.AddRange(temp);
}
private IEnumerable<T> MapResults(IEnumerable<TEntity> results)
{
return results.Select(x => _repository.ObjectMapper.Map<T>(x));
}
}
public class TestEntity : BaseFileEntity
{
}
But its giving error on line returnVal.AddRange(temp).
cannot convert from System.Collections.Generic.List<TestEntity> to
System.Collections.Generic.IEnumerable<T>
As suggested by #Camilo Terevinto and #mjwills return (IEnumerable<T>)retVal; had given me a hint to proceed to solve my issue. I'm posting my answer so that it can help others. Thanks everyone for your help, your comments really helped.
public async Task<IEnumerable<T>> TestMethod(IEnumerable<MasterDataFileOperationItem> items)
{
var ids = items.Select(i => i.Id).ToArray().Select(x => x.ToString()).ToArray();
var result = await _repository.GetAll().Where(x => ids.Contains(x.Id)).ToListAsync();
var returnVal = MapResults(result).ToList();
var temp = CreateTestEntity(testNos);
returnVal.AddRange(temp);
return returnVal;
}
public IEnumerable<T> CreateTestEntity(string[] testNos)
{
List<TestEntity> ls = new List<TestEntity>();
foreach (var testNo in testNos)
{
var TestEntity = new TestEntity
{
TestNo = testNo,
};
ls.Add(TestEntity);
}
return (IEnumerable<T>) ls;
}

how to yield an anonymous class in IEnumerable.GroupBy<T> "on the fly" (without enumerating result) in C# LINQ?

I do like this now (thanks to StackOverflow):
IEnumerable<object> Get()
{
var groups = _myDatas.GroupBy(
data => new { Type = data.GetType(), Id = data.ClassId, Value = data.Value },
(key, rows) => new
{
ClassId = key.Id,
TypeOfObject = key.Type,
Value = key.Value,
Count = rows.Count()
}));
foreach (var item in groups)
{
yield return item;
}
}
IEnumerable<MyData> _myDatas;
But is possible to make faster or more "elegant" by not having last foreach loop, but yielding it when the group/anonymous class instance is created?
I would guess fastest way would be to write it open and:
sort the _myDatas
enumerate it and when group changes yield the last group
But I'm trying to learn some LINQ (and C# features in general) so I don't want to do that.
The rest of example is here:
public abstract class MyData
{
public int ClassId;
public string Value;
//...
}
public class MyAction : MyData
{
//...
}
public class MyObservation : MyData
{
//...
}
You should be able to return groups directly, though you might need to change your return type from IEnumerable<Object> to just IEnumerable.
So:
IEnumerable Get()
{
var groups = _myDatas.GroupBy(
// Key selector
data => new {
Type = data.GetType(),
Id = data.ClassId,
Value = data.Value
},
// Element projector
(key, rows) => new
{
ClassId = key.Id,
TypeOfObject = key.Type,
Value = key.Value,
Count = rows.Count()
}
);
return groups;
}
groups has the type IEnumerable< IGrouping< TKey = Anonymous1, TElement = Anonymous2 > >, so you can return it directly.

method returns dynamic type of List

If I have the following:
class Super {
//stuff
}
class Sub1 : Super {
//stuff
}
class Sub2 : Super {
//stuff
}
class Sub3 : Super {
//stuff
}
Run Class
class Run {
//list of type Super with a bunch of sub objects (sub1,sub2,sub3)
List<Super> superList = sub1,2,3 list;
List<Super> partialSuperList;
void doStuff() {
Type subObjectType = superList[0].GetType();
if (subObjectType == typeof(sub1)) {
partialSuperList = categorize(subObjectType);
} else if (subObjectType == typeof(sub2)) {
partialSuperList = categorize(subObjectType);
} else if (subObjectType == typeof(sub3)) {
partialSuperList = categorize(subObjectType);
}
}
List<Super> categorize(Type type) {
List<type> subTypeList = new List<type>();
//loop through subtype list and add it to a super type list
return SuperList;
}
}
as you can see I'm trying to dynamically create a list with a "type" coming from the argument to the method, am I doing this right or is there another way to do this?
It's hard to judge if you are doing the right thing since your pseudo code doesn't compile at the moment.
I think you are trying filter a list based on some type. I think the Linq method OfType<TResult> is what you are looking for.
var superList = new List<Super> { ... };
var sub1List = superList.OfType<Sub1>().ToList();
var sub2List = superList.OfType<Sub2>().ToList();
var sub3List = superList.OfType<Sub3>().ToList();
If you are want to filter on a type you do not know at compile-time, you can do the following:
var superList = new List<Super> { ... };
var someType = superList.First().GetType();
var filteredSuperList = superList.Where(x => x.GetType() == someType).ToList();
Make the method generic:
List<T> doStuff<T>() where T : Super
{
List<T> list = new List<T>();
// use list
}
You don't have the casting problems you have now, and you can check T using is:
if (T is Sub1)
{
... // do something
}

How do I create an Extension method that converts a List<T> to another List<T> of a different type at runtime?

I know this has been asked before, but the solutions I have tried never take into consideration nullable types.
I need something that will be able to handle a conversion like
List<string> to List<Int32?>, List<string> to List<int>, List<string>
to List<double>
etc.
I am trying to create something as follows
private void RoutineCompleted(string category, List Type fieldType = null)
{
//ToNewType is the extension method that I need.
var var convertedList = values.ToNewType(fieldType);
}
I have looked at the following code, but it does not do the job:
public static IEnumerable Cast(this IEnumerable self, Type innerType)
{
var methodInfo = typeof(Enumerable).GetMethod("Cast");
var genericMethod = methodInfo.MakeGenericMethod(innerType);
return genericMethod.Invoke(null, new object[] { self }) as IEnumerable;
}
Can anyone help me figure this out?
Thanks!
You do not need an extension if you can convert using Convert.To... methods
List<string> strList = ...
List<int> intList = strList.Select(s => Convert.ToInt32(s)).ToList();
or you can use Convert.ChangeType
public static IEnumerable<TOut> ConvertTo<TIn, TOut>(this IEnumerable<TIn> list)
{
return list.Select(o => (TOut)Convert.ChangeType(o, typeof (TOut)));
}
List<string> strList = new List<string>();
IEnumerable<int> intList = strList.ConvertTo<string, int>();
The conversions you're talking about are not casts per se, but type conversions.
You can do them using Linq, for example like this:
var ints = new List(){1,2,3,4,51};
var strings = array.Select(x => x.ToString());
or
var strings = new List() {"1.56","2.71","3.14"};
var doubles = strings.Select( x => Convert.ToDouble(x));
Those give you IEnumerables, but you can make them into Lists using .ToList().
the Convert class cannot deal with Nullable types, so I had to special-case those.
static void Main(string[] args)
{
var strings = new[] { "1","2","3"};
var ints = strings.ConvertItems<string, int>().ToList();
var doubles = strings.ConvertItems<string, double>().ToList();
var nullableints = strings.ConvertItems<string, int?>().ToList();
}
public static IEnumerable<TargetType> ConvertItems<SourceType, TargetType>(this IEnumerable<SourceType> sourceCollection)
{
var targetType = typeof(TargetType);
if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
targetType = targetType.GetGenericArguments().First();
}
return sourceCollection.ConvertItems((source) => (TargetType)Convert.ChangeType(source, targetType));
}
public static IEnumerable<TargetType> ConvertItems<SourceType, TargetType>(this IEnumerable<SourceType> sourceCollection, Converter<SourceType, TargetType> convertor)
{
foreach (var item in sourceCollection)
{
yield return convertor(item);
}
}
EDIT: weakly-typed version, when you only know the type at runtime
static void Main(string[] args)
{
var strings = new[] { "1","2","3"};
var ints = strings.ConvertItems(typeof(int));
var doubles = strings.ConvertItems(typeof(double));
var nullableints = strings.ConvertItems(typeof(int?));
foreach (int? item in nullableints)
{
Console.WriteLine(item);
}
}
public static IEnumerable ConvertItems(this IEnumerable sourceCollection,Type targetType)
{
if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
targetType = targetType.GetGenericArguments().First();
}
foreach (var item in sourceCollection)
{
yield return Convert.ChangeType(item, targetType);
}
}
This is a simple LINQ Select:
IEnumerable<string> values = new[] { "1", "2", "3" };
var converted = values.Select(PerformConversion);
private int? PerformConversion(string input)
{
...
}
You will need a different method for each conversion, or a parameterised method to encompassed all of them. For example:
private T? PerformConversion<T>(string input) where T : struct, IConvertible
{
return (T?) Convert.ChangeType(input, typeof(T));
}
You can try with this:
public static IEnumerable<TTo> ChangeType<TTo, TFrom>(this IEnumerable<TFrom> self)
{
return self.Select(e => (TTo)Convert.ChangeType(e, typeof(TTo)));
}
EDIT:
If you don't wan't to use generic's and you have to pass type: Type like this:
var list = new List<string> {"1", "3", "4", null};
var result = list.ChangeType(typeof (int?));
You can try use this:
public static IEnumerable ChangeType(this IEnumerable self, Type type)
{
var converter = TypeDescriptor.GetConverter(type);
foreach (var obj in self)
{
yield return converter.ConvertFrom(obj);
}
}

Categories

Resources