How to access anonymous method from generic list? - c#

I've been working on a library to generate fake data using Faker.NET. The problem I'm having is that I don't know how to access an anonymous method that I'm passing to the constructor of my DataGenerator child classes.
The issue is that in order to create a list of generics I had to create base class DataGenerator but I cannot pull my Func<T> member up because that base class is not generic so no Tavailable. However, my DataGenerator<T> class does expose the Generator property which is my anonymous method but I haven't found a way to access it while iterating my list of data generators.
Any advice will be highly appreciated.
This is what I have so far:
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Guid EmpUid { get; set; }
}
// Define other methods and classes here
public abstract class DataGenerator
{
public abstract int GetWeight(string matchingProperty);
public abstract Type Type { get;}
}
public abstract class DataGenerator<T> : DataGenerator
{
public readonly string[] Tags;
public readonly Func<T> Generator;
protected DataGenerator(Func<T> generator, params string[] tags)
{
Tags = tags;
//How to access this?
Generator = generator;
}
public override int GetWeight(string matchingProperty)
{
int sum = (from tag in Tags
where matchingProperty.ToLowerInvariant().Contains(tag.ToLowerInvariant())
select 1).Sum();
return sum;
}
public override Type Type {
get { return typeof(T); }
}
}
public class StringDataGenerator : DataGenerator<string>
{
public StringDataGenerator(Func<string> generator, params string[] tags) : base(generator, tags)
{
}
}
public class GuidDataGenerator : DataGenerator<Guid>
{
public GuidDataGenerator(Func<Guid> generator, params string[] tags)
: base(generator, tags)
{
}
}
And I'm testing it here:
private static void Main(string[] args)
{
var dataGeneratorList = new List<DataGenerator>
{
new StringDataGenerator(Name.First, "first", "name"),
new StringDataGenerator(Name.Last, "last", "name"),
new GuidDataGenerator(Guid.NewGuid, "uid", "id")
};
var writeProperties = typeof (Employee).GetProperties().Where(p => p.CanWrite);
foreach (var property in writeProperties)
{
foreach (var dataGenerator in dataGeneratorList)
{
if (property.PropertyType == dataGenerator.Type)
{
var weigth = dataGenerator.GetWeight(property.Name);
//How to access generator here???
var testValue = dataGenerator.Generator.Invoke();
}
}
}
}

As you tagged, given your current setup, reflection is probably your only option.
var func = dataGenerator.GetType().GetField("Generator").GetValue(dataGenerator);
var testValue = func.GetType().GetMethod("Invoke").Invoke(func, null);
I'm not sure anyone could call this super nice, and it won't be super fast, but it's probably sufficient for anything you need fake data in, I suppose.
For good measure, here's it in action.
Your question is actually a bit more complicated than it may seem at face-value. A nice way of handling this if you only ever use it in object form is just to add an abstract Generate method to the base, non-generic class:
public abstract object Generate();
Then override it in your generic one:
public override object Generate()
{
return this.Generator();
}
Of course, this return an object, which isn't nice in a generic class. But at least it avoids reflection.
Another solution to avoid this reflection nonsense might be the use of covariance, although that will, unfortunately, break for value types, like Guid.
public interface IDataGenerator<out T>
{
int GetWeight(string matchingProperty);
Type Type { get;}
T Generate();
}
public abstract class DataGenerator<T> : IDataGenerator<T>
{
public readonly string[] Tags;
public readonly Func<T> Generator;
protected DataGenerator(Func<T> generator, params string[] tags)
{
Tags = tags;
//How to access this?
Generator = generator;
}
public T Generate(){
return this.Generator();
}
. . .
}
That then turns into a preferable,
private static void Main(string[] args)
{
var dataGeneratorList = new List<IDataGenerator<object>>
{
new StringDataGenerator(Name.First, "first", "name"),
new StringDataGenerator(Name.Last, "last", "name")
// But this line doesn't work
// new GuidDataGenerator(Guid.NewGuid, "uid", "id")
};
var writeProperties = typeof (Employee).GetProperties().Where(p => p.CanWrite);
foreach (var property in writeProperties)
{
foreach (var dataGenerator in dataGeneratorList)
{
if (property.PropertyType == dataGenerator.Type)
{
var weigth = dataGenerator.GetWeight(property.Name);
var testValue = dataGenerator.Generate();
}
}
}
}

Related

Generic class and method binding with delegates(or not) for clean method expression

Lets say you have simple chained method but you are trying to access or set a value in a class property (internal/external doesnt matter). Using a Func seems to be working and finds the relation between generic class that is passed and access its properties correctly but i am not sure if its necessary.
Is there a way of setting the method variable cleanly as in Main method below since it is aware of the Generic class association without doing new Props().Property for example?
//sample console app
public class Props {
public string FirstProp = "lets say object";
public string SecondProp = "Pretend some other object";
}
public class Logic<T> where T : class, new()
{
private string outString { get; set; }
public Logic<T> GetPropertyValue(Func<T, object> propertySelector)
{
return this;
}
public Logic<T> GetLambda(Expression<Func<T, object>> propertySelector)
{
var breakpointCheck = propertySelector; //{x => x.SecondProp}
return this;
}
}
class Program
{
static void Main(string[] args)
{
var Test =
new Logic<Props>()
.GetPropertyValue(x => x.FirstProp) //dummy check
.GetLambda(x => x.SecondProp); //passed correctly {x => x.SecondProp}
var HowToGetThis =
new Logic<Props>()
.GetPropertyValue(FirstProp) // or GetPropertyValue(Props.FirstProp)
.GetLambda(x => x.SecondProp);
}
}

Class with generic parameter of the same generic class type

I had a class which represents a prefix search tree:
public class PrefixTree<TData>
{
private PrefixTree<TData>[] _children;
private void SomeMethod()
{
_children = new PrefixTree<TData>[10];
}
}
Then I created a derived class with additional features for its nodes:
public class NewPrefixTree<TData> : PrefixTree<TData>
The problem is that in SomeMethod() of derived class we still create instances of base class and it doesn't fit the meaning.
I refactored the base class to this:
public abstract class PrefixTree<TData, TNode>
where TNode : PrefixTree<TData, TNode>, new()
{
private TNode[] _children;
private void SomeMethod()
{
_children = new TNode[10];
}
}
Despite the base class has complete functionality itself, I had to make it abstract because I can't write new DigitalPrefixTree<TData, DigitalPrefixTree<int, ...>>() .
But now I can use it this way and it works perfectly:
public class NewPrefixTree<TData> : PrefixTree<TData, NewPrefixTree<TData>> {} // for derived class
//or
public class PrefixTree<TData> : PrefixTree<TData, PrefixTree<TData>> {} // to use the base functionality
I've never done this before and I wonder if it's a good idea to declare a class with generic parameter of the same generic class type. Or I need to make some tricks with co/contra-variance of generic interfaces (but it probably doesn't work as I use the class type as method’s parameters type and as return type as well)?
Try this:
public interface INode<out TData>
{
TData Data { get; }
IEnumerable<INode<TData>> Children { get; }
public IEnumerable<TRequiredData> GetNestedData<TRequiredData>();
}
public interface ITree<out TData>
{
IEnumerable<INode<TData>> Children { get; }
IEnumerable<TRequiredData> GetNestedData<TRequiredData>();
}
public class Node<TData> : INode<TData>
{
public TData Data { get; }
public IEnumerable<INode<TData>> Children { get; }
public Node(TData data, IEnumerable<INode<TData>>? children = null)
{
Data = data;
Children = children ?? Enumerable.Empty<INode<TData>>();
}
public IEnumerable<TRequiredData> GetNestedData<TRequiredData>()
{
List<TRequiredData> result = new();
if (Data is TRequiredData requiredData)
result.Add(requiredData);
foreach (var child in Children)
result.AddRange(child.GetNestedData<TRequiredData>());
return result;
}
}
public class Tree<TData> : ITree<TData>
{
public IEnumerable<INode<TData>> Children { get; }
public Tree(IEnumerable<INode<TData>> children)
{
Children = children;
}
public IEnumerable<TRequiredData> GetNestedData<TRequiredData>()
{
List<TRequiredData> result = new();
foreach (var node in Children)
result.AddRange(node.GetNestedData<TRequiredData>());
return result;
}
}
And here is example of usage:
record SomeRecord();
class SomeClass {}
static void Main(string[] args)
{
var nodeWithNested = new Node<SomeClass>(
data: new SomeClass(),
children: new List<INode<SomeClass>>()
{
new Node<SomeClass>(new SomeClass()),
new Node<SomeClass>(new SomeClass())
});
var nodes = new List<INode<object>>()
{
new Node<SomeClass>(new SomeClass()),
nodeWithNested,
new Node<SomeRecord>(new SomeRecord()),
};
var tree = new Tree<object>(nodes);
var someClasses = tree.GetNestedData<SomeClass>(); // 4 items
var someRecords = tree.GetNestedData<SomeRecord>(); // 1 item
}
This approach based on out generic modifier.
The only restriction is that you can not use structs (int, bool and ect.) as they don't have common object to cast to.
Hope this will be useful.

Advice on using the Factory Pattern for converting a collection of model objects to one of DTOs and vice versa

I'm trying to convert a collection of model objects that share a common parent, into one of DTOs. Likewise, I want to reverse the procedure - taking a collection of DTOs with a common parent into one of model objects.
From what I've read, a Factory Pattern seems to be what I'm looking for. I also have a Producer class that handles the conversion between object model and DTO by calling the relevant factory method.
There are a few limitations:
This is an open source library, and I don't want to add methods to existing classes. Otherwise a visitor pattern would have worked. Please correct me if I'm wrong.
Similarly, I don't want to add any additional packages to this project. From what I understand, AutoMapper would have been one of the ways to go about this.
I'm new(ish) to C# and design patterns, so I apologize if I am doing something that doesn't make sense.
Here is some sample code representing what I've tried so far. I used some references from online to get an idea, but something about it doesn't seem right. There was another way mentioned here: Is a switch statement applicable in a factory method? c#, but I'm not sure if that is transferrable to this scenario.
Any critique or suggestions is welcome.
Example Usage
Animal pet1 = new Pigeon("Pidgey", 100, false);
Animal pet2 = new Rattlesnake("Ekans", 20.0, true);
IList<Animal> myPets = new List<Animal>() { pet1, pet2 };
AnimalDTOProducer dtoProducer = new AnimalDTOProducer(new AnimalDTOFactory());
IList<AnimalDTO> myDTOs = new List<AnimalDTO>();
myDTOs = dtoProducer.ConvertAnimalCollection(myPets);
Models
public abstract class Animal
{
public Animal(string name)
{
Name = name;
}
public string Name { get; set; }
// business logic
}
public abstract class Bird : Animal
{
public Bird(string name, int maxAltitude, bool isReal)
: base(name)
{
Name = name;
MaxAltitude = maxAltitude;
IsReal = isReal;
}
public int MaxAltitude { get; set; }
public bool IsReal { get; set; }
// business logic
}
public class Pigeon : Bird
{
public Pigeon(string name, int maxAltitude, bool isReal)
: base(name, maxAltitude, isReal)
{
}
// business logic
}
public abstract class Snake : Animal
{
public Snake(string name, double length, bool isPoisonous)
: base(name)
{
Name = name;
Length = length;
IsPoisonous = isPoisonous;
}
public double Length { get; set; }
public bool IsPoisonous { get; set; }
// business logic
}
public class Rattlesnake : Snake
{
public Rattlesnake(string name, double length, bool isPoisonous)
: base(name, length, isPoisonous)
{
}
// business logic
}
DTOs
public abstract class AnimalDTO { }
public class PigeonDTO : AnimalDTO
{
public string Name { get; set; }
public int MaxAltitude { get; set; }
public bool IsReal { get; set; }
}
public class RattlesnakeDTO : AnimalDTO
{
public string Name { get; set; }
public double Length { get; set; }
public bool IsPoisonous { get; set; }
}
Factories
public interface IFactory { }
public interface IAnimalFactory : IFactory
{
Animal CreateAnimal(AnimalDTO DTO);
}
public interface IAnimalDTOFactory : IFactory
{
AnimalDTO CreateAnimalDTO(Animal animal);
}
public class AnimalFactory : IAnimalFactory
{
public Animal CreateAnimal(AnimalDTO DTO)
{
switch (DTO)
{
case PigeonDTO _:
var pigeonDTO = (PigeonDTO)DTO;
return new Pigeon(pigeonDTO.Name, pigeonDTO.MaxAltitude, pigeonDTO.IsReal);
case RattlesnakeDTO _:
var rattlesnakeDTO = (RattlesnakeDTO)DTO;
return new Rattlesnake(rattlesnakeDTO.Name, rattlesnakeDTO.Length, rattlesnakeDTO.IsPoisonous);
// And many more ...
default:
return null;
}
}
}
public class AnimalDTOFactory : IAnimalDTOFactory
{
public AnimalDTO CreateAnimalDTO(Animal animal)
{
switch (animal)
{
case Pigeon _:
var _pigeon = (Pigeon)animal;
return new PigeonDTO()
{
Name = _pigeon.Name,
MaxAltitude = _pigeon.MaxAltitude,
IsReal = _pigeon.IsReal
};
case Rattlesnake _:
var _rattlesnake = (Rattlesnake)animal;
return new RattlesnakeDTO()
{
Name = _rattlesnake.Name,
Length = _rattlesnake.Length,
IsPoisonous = _rattlesnake.IsPoisonous
};
// And many more ...
default:
return null;
}
}
}
Producers
public interface IProducer { }
public interface IAnimalProducer : IProducer
{
Animal ProduceAnimalFromDTO(AnimalDTO DTO);
}
public interface IAnimalDTOProducer : IProducer
{
AnimalDTO ProduceAnimalDTOFromAnimal(Animal animal);
}
public class AnimalProducer : IAnimalProducer
{
private IAnimalFactory factory;
public AnimalProducer(IAnimalFactory factory)
{
this.factory = factory;
}
public IList<Animal> ConvertAnimalDTOCollection(IList<AnimalDTO> DTOCollection)
{
IList<Animal> result = new List<Animal>();
foreach (AnimalDTO DTO in DTOCollection)
{
var dto = ProduceAnimalFromDTO(DTO);
if (dto != null)
result.Add(dto);
}
return result;
}
public Animal ProduceAnimalFromDTO(AnimalDTO animalDTO)
{
return this.factory.CreateAnimal(animalDTO);
}
}
public class AnimalDTOProducer : IAnimalDTOProducer
{
private IAnimalDTOFactory factory;
public AnimalDTOProducer(IAnimalDTOFactory factory)
{
this.factory = factory;
}
public IList<AnimalDTO> ConvertAnimalCollection(IList<Animal> collection)
{
IList<AnimalDTO> result = new List<AnimalDTO>();
foreach (Animal animal in collection)
{
var _animal = ProduceAnimalDTOFromAnimal(animal);
if (_animal != null)
result.Add(_animal);
}
return result;
}
public AnimalDTO ProduceAnimalDTOFromAnimal(Animal animal)
{
return this.factory.CreateAnimalDTO(animal);
}
}
UPDATE 1
As recommended by sjb-sjb and ChiefTwoPencils in the comments, I eliminated the switch statements from the respective factories. The result looks like this:
public class AnimalFactory : IAnimalFactory
{
public Animal CreateAnimal(AnimalDTO DTO)
{
Type srcType = DTO.GetType();
Type modelType = Type.GetType(Regex.Replace(srcType.FullName, #"(DTO)$", ""));
IList<PropertyInfo> props = new List<PropertyInfo>(srcType.GetProperties());
var propVals = props.Select(prop => prop.GetValue(DTO, null)).ToArray();
Animal animal = (Animal)Activator.CreateInstance(modelType, propVals);
return animal;
}
}
public class AnimalDTOFactory : IAnimalDTOFactory
{
public AnimalDTO CreateAnimalDTO(Animal animal)
{
Type srcType = animal.GetType();
Type dtoType = Type.GetType($"{srcType.FullName}DTO");
AnimalDTO dto = (AnimalDTO)Activator.CreateInstance(dtoType, new object[] { });
foreach (PropertyInfo dtoProperty in dtoType.GetProperties())
{
PropertyInfo srcProperty = srcType.GetProperty(dtoProperty.Name);
if (srcProperty != null)
{
dtoProperty.SetValue(dto, srcProperty.GetValue(animal));
}
}
return dto;
}
}
The one thing I forgot to mention in the original question was that the constructor for the model may have more arguments than the DTO object has properties. That, and the order of arguments may not be the same. I think in pseudo-code, a solution will look something like this:
void AssignParamsToConstructor()
{
// Extract constructer parameters with names into an ordered list
// Match DTO properties with extracted parameters via name and type
// Fill any missing parameters with a default value or null
// Pass the final list of parameters as an array to Activator.CreateInstance method
}
I will be researching on a way to resolve this for the time being, but any pointers will be welcome.
UPDATE 2
Okay, so I found a kind of hacky solution for the previous problem regarding calling the Model constructor with missing or out-of-order arguments.
I created a helper class that creates an ordered argument array based on a combination of the Model constructor arguments and the DTO properties. This array can then be passed to Activator.CreateInstance without causing any issues.
Here is the updated AnimalFactory.CreateAnimal method:
public Animal CreateAnimal(AnimalDTO DTO)
{
Type srcType = DTO.GetType();
Type modelType = Type.GetType(Regex.Replace(srcType.FullName, #"(DTO)$", ""));
object[] propVals = Helpers.GenerateConstructorArgumentValueArray(modelType, DTO);
Animal animal = (Animal)Activator.CreateInstance(modelType, propVals);
return animal;
}
And here is the helper class:
public static class Helpers
{
public static object[] GenerateConstructorArgumentValueArray(Type type, object obj)
{
IList<(string, Type)> ctorArgTypes = new List<(string, Type)>();
IList<(string, object)> propVals = new List<(string, object)>();
// Get constructor arguments
ctorArgTypes = GetConstructorArgumentsAndTypes(type);
// Get object properties
propVals = GetObjectPropertiesAndValues(obj);
// Create args array
IList<object> paramVals = new List<object>();
foreach (var ctorArg in ctorArgTypes)
{
object val;
string _name = ctorArg.Item1.ToLower();
(string, object) _namedProp = propVals.Where(prop => prop.Item1.ToLower() == _name).FirstOrDefault();
if (_namedProp.Item2 != null)
{
val = _namedProp.Item2;
}
else
{
val = ctorArg.Item2.IsValueType ? Activator.CreateInstance(ctorArg.Item2) : null;
}
paramVals.Add(val);
}
return paramVals.ToArray();
}
private static IList<(string, Type)> GetConstructorArgumentsAndTypes(Type type)
{
List<(string, Type)> ctorArgs = new List<(string, Type)>();
TypeInfo typeInfo = type.GetTypeInfo();
ConstructorInfo[] ctors = typeInfo.DeclaredConstructors.ToArray();
ParameterInfo[] ctorParams = ctors[0].GetParameters();
foreach (ParameterInfo info in ctorParams)
{
ctorArgs.Add((info.Name, info.ParameterType));
}
return ctorArgs;
}
private static IList<(string, object)> GetObjectPropertiesAndValues(object obj)
{
List<(string, object)> props = new List<(string, object)>();
PropertyInfo[] propInfo = obj.GetType().GetProperties();
foreach (PropertyInfo info in propInfo)
{
string name = info.Name;
object val = info.GetValue(obj);
props.Add((name, val));
}
return props;
}
}
I'll have to look at this later to see how it can be improved on. For the time being however, it does its job.
I would appreciate any comments or input if you have any. I will keep updating this post until I find an absolute solution.
So I worked out a solution that seems to achieve my original goal.
The reason it was difficult to solve at first was due to the original Factory class having too many responsibilities. It had to map the properties and create a new object. Separating these made it easy to implement the Generic Factory suggested by this post:
https://web.archive.org/web/20140414013728/http://tranxcoder.wordpress.com/2008/07/11/a-generic-factory-in-c
I created a simple mapper that would automatically map Entity and DTO properties. The easier solution is to use an AutoMapper like grandaCoder suggested. My situation required otherwise so a custom mapper was the way to go. I also tried to minimize calls to System.Reflection so the performance wouldn't suffer too much.
The end result is a Factory that can convert between any Entity and DTO object, maps properties between them, and can instantiate an Entity class with no default / empty constructor.
I ended up making a lot more changes to the original post, so I uploaded the end result to github: https://github.com/MoMods/EntityDTOFactory
I am open to any additional ideas / criticisms on the final solution. This is my first time solving this kind of problem, so it's very likely there are some better ideas out there.
Thanks again for the help and suggestions!
The switch statement can be avoided using reflection:
public AnimalDTO ToDTO( Animal src)
{
Type srcType = src.GetType();
Type dtoType = Type.GetType(srcType.Name + "DTO");
AnimalDTO dto = (AnimalDTO)Activator.CreateInstance(dtoType, new object[] { });
foreach (PropertyInfo dtoProperty in dtoType.GetProperties()) {
PropertyInfo srcProperty = srcType.GetProperty(dtoProperty.Name);
if (srcProperty != null) {
dtoProperty.SetValue(dto, srcProperty.GetValue(src));
}
}
return dto;
}
To get a FromDTO method, just reverse the roles of src and dto in ToDTO.
I would not reinvent the wheel on this common scenario.
https://automapper.org/
https://www.nuget.org/packages/automapper/
OR
https://github.com/MapsterMapper/Mapster
https://www.nuget.org/packages/Mapster/
.......
Learn how to use one of these frameworks.
Below is mapster..........."performance" numbers.......which is how I found it (someone told me to lookout for automapper performance)

How do I get a List<T> property in a class via reflection in C#?

I have a class called Animals what contains two List<T>.
One is a list of bears, and one is a list of pinguins.
I can pretty trivially get the list of bears by just calling Bears on the animals variable - but how do I get it via reflection?
I have created a static helper class ListHelper, with a generic method that takes either Bear or Pinguin as the generic type, and animals as the argument which should return the list of bears if Bear is the generic type.
That doesn't happen. Instead it crashes with this message
: System.Reflection.TargetException: 'Object does not match target type, and I cannot understand why, because the type is correct when I inspect it via the debugger.
Fully "working" example below.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System;
class ReflectionTrouble
{
static void Main(string[] args)
{
var animals = new Animals
{
Bears = new List<Bear> {new Bear {Name = "Bear1"}, new Bear {Name = "Bear 2"}},
Pinguins = new List<Pinguin> {new Pinguin {Name = "Pingo1"}, new Pinguin {Name = "Pingo2"}}
};
var lists = ListHelper.GetList<Bear>(animals);
foreach (var bear in lists)
{
Console.WriteLine(bear.Name);
}
//expected to have printed the names of the bears here...
}
}
public static class ListHelper
{
public static IEnumerable<T> GetList<T>(Animals animals)
{
var lists = animals.GetType().GetRuntimeProperties().Where(p => p.PropertyType.IsGenericType);
foreach (var propertyInfo in lists)
{
var t = propertyInfo.PropertyType;
var typeParameters = t.GetGenericArguments();
foreach (var typeParameter in typeParameters)
{
if (typeParameter == typeof(T))
{
// This is where it crashes.
var list = (IEnumerable<T>) propertyInfo.GetValue(t);
return list;
}
}
}
return Enumerable.Empty<T>();
}
}
public class Animals
{
public IList<Bear> Bears { get; set; }
public IList<Pinguin> Pinguins { get; set; }
}
public class Bear
{
public string Name { get; set; }
}
public class Pinguin
{
public string Name { get; set; }
}
You are misunderstanding how to call propertyInfo.GetValue. The parameter you are passing to it should be the object whose property value you want to get. So in this case you are wanting the value of the property on animals so that line should be:
var list = (IEnumerable<T>) propertyInfo.GetValue(animals);
With this change your code is returning bears to me.
I think a dictionary would be better solution here instead of reflection. Out of the box a dictionary should be able to handle all of your scenarios and offer much better performance. If you want to encapsulate it in a class then it might look like this.
public interface IAnimal
{
string Name { get; set; }
}
public class Animals
{
private readonly ConcurrentDictionary<Type, IList<IAnimal>> AnimalDictionary;
public Animals(IList<IAnimal> animals)
{
this.AnimalDictionary = new ConcurrentDictionary<Type, IList<IAnimal>>();
this.Add(animals);
}
public Animals(IAnimal animal)
{
this.AnimalDictionary = new ConcurrentDictionary<Type, IList<IAnimal>>();
this.Add(animal);
}
public IList<IAnimal> Get<T>() where T : IAnimal
{
if (this.AnimalDictionary.ContainsKey(typeof(T)))
{
return this.AnimalDictionary[typeof(T)];
}
return (IList <IAnimal>)new List<T>();
}
public void Add(IList<IAnimal> animals)
{
foreach (IAnimal animal in animals)
{
this.Add(animal);
}
}
public void Add(IAnimal animal)
{
this.AnimalDictionary.AddOrUpdate(animal.GetType(),
new List<IAnimal>{animal},
(type, list) =>
{
list.Add(animal);
return list;
});
}
}

Generic Class to create a Generic Collection using IEnumerable<T>

This is a Two (2) Part Question about Generics
I've got to create several similar classes to model similarly designed database tables.
All tables contain an ID int and a Text nvarchar(50) field. One or two may contain a few other fields.
I rarely use generics, but I see examples of it on here quite frequently. This is my largest attempt to create a generic class that is used in another generic class.
My basic construct is as follows, and I will point out with a comment what does not work and the error message Visual Studio 2010 is displaying:
public class IdText {
public IdText(int id, string text) {
ID = id;
Text = text;
}
public int ID { get; private set; }
public string Text { get; private set; }
}
public class TCollection<T> : IEnumerable<T> where T : IdText {
private List<T> list;
public TCollection() {
list = new List<T>();
}
public void Add(int id, string text) {
foreach (var item in list) {
if (item.ID == id) {
return;
}
}
list.Add(new T(id, text)); // STOP HERE
// Cannot create an instance of the variable type 'T'
// because it does not have the new() constraint
}
public T this[int index] {
get {
if ((-1 < 0) && (index < list.Count)) {
return list[index];
}
return null;
}
}
public T Pull(int id) {
foreach (var item in list) {
if (item.ID == id) {
return item;
}
}
return null;
}
public T Pull(string status) {
foreach (var item in list) {
if (item.Text == status) {
return item;
}
}
return null;
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator() {
foreach (var item in list) yield return item;
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return list.GetEnumerator();
}
#endregion
}
Visual Studio's IntelliSence wants me to add list.Add(T item), but I need to create this first.
I have attempted to re-write the offending line list.Add(new T(id, text)); as list.Add(new IdText(id, text));, but then I am reprimanded with the message "cannot convert from IdText to T".
How exactly do I get around this?
Next: When I go in to actually create a version of this IdText class later, I am not sure how exactly I can use this new class in the TCollection class I have designed for it.
For example, given this derived class:
public class ManufacturedPart : IdText {
public ManufacturedPart(int id, string partNum, string description)
: base(int id, string partNum) {
Description = description;
}
public string Description { get; private set; }
}
...would I need to also derive a special version of TCollection to accompany it, like so?
public class ManufacturedParts<T> : IEnumerable<T> where T : ManufacturedPart {
// OK, now I'm lost! Surely this can't be right!
}
1) You could use the new() constraint, make your properties public and add a parameterless constructor:
public class IdText
{
public IdText()
{
}
public IdText(int id, string text)
{
ID = id;
Text = text;
}
public int ID { get; set; }
public string Text { get; set; }
}
public class TCollection<T> : IEnumerable<T> where T : IdText, new()
{
private List<T> list;
public TCollection()
{
list = new List<T>();
}
public void Add(int id, string text)
{
foreach (var item in list)
{
if (item.ID == id)
{
return;
}
}
list.Add(new T { ID = id, Text = text });
}
}
2) You have multiple options:
If you want your collection to store any IdText (ManufacturedPart or anything else that derived from IdText):
TCollection<IdText> ss = new TCollection<IdText>();
The above, for now, can only store IdText as you instantiate objects in the Add(int, string) method, but if you provide a Add(T object) method, it could store any IdText instance.
If you want your collection to only contains ManufacturedParts:
public class ManufacturedParts<T> : TCollection<T> where T : ManufacturedPart, new()
{
// Provide here some specific implementation related to ManufacturedParts
// if you want. For example, a TotalPrice property if ManufacturedPart
// has a Price property.
}
TCollection<ManufacturedPart> ss2 = new ManufacturedParts<ManufacturedPart>();
or even simpler, if your collection doesn't provide any additional method depending on the type of the stored objects:
TCollection<ManufacturedPart> ss2 = new TCollection<ManufacturedPart>();
Even simpler, if your goal is to only store objects, a custom collection isn't needed:
List<IdText> ss2 = new List<IdText>(); // Uses the built-in generic List<T> type
About the first question: c# doesn't support constructors with parameters as a generic constrain. Something you can do is replace it with
(T)Activator.CreateInstance(typeof(T),new object[]{id,text});
By the other hand... you don't actually know how the constructors of the derived class will look like, so you can't ensure they will have that constructor.
About the second question, you can do this:
var collection = new TCollection<ManufacturedPart>();
in the same way List works.
Hope it helps.
If your collection class will be responsible for instantiating elements of its collected type, then you probably don't want to be using either the new() constraint or Activator.CreateInstance() -- as Jon Skeet has blogged, both of these exhibit poor performance.
It sounds like what you actually want is a provider delegate, like so:
public class MyCollection<T> : IEnumerable<T> where T : IdText {
private readonly List<T> list;
private readonly Func<int, string, T> provider;
public MyCollection(Func<int, string, T> provider) {
this.list = new List<T>();
this.provider = provider;
}
public void Add(int id, string text) {
list.Add(provider(id, text));
}
}
And then you'd use it like:
var collection = new MyCollection((id, text) => new ManufacturedPart(id, text));
You can think of this as passing the specific constructor you want to use as an argument into the class, which it then uses to construct instances as needed.
And you don't need to create a separate subclass for MyCollection<ManufacturedPart> -- just use the generic class directly.

Categories