How can I convert these two ConvertDtoListToAddresses and ConvertDtoListToDocuments C# methods to a generic? I've tried passing in two generic type variables, but when I get down to 'Add' in the loop I get stuck on various errors. Converting from a DTO to its respective DBO is done in the constructor of the DBO, which I think is part of the problem.
private void ConvertDtoToPerson(PersonDTO dto)
{
Id = dto.Id;
Fname = dto.FirstName;
Mname = dto.MiddleName;
Lname = dto.LastName;
Suffix = dto.Suffix;
Maiden = dto.MaidenName;
Addresses = ConvertDtoListToAddresses(dto.AddressesDto); // want to convert to generic
Documents = ConvertDtoListToDocuments(dto.DocumentsDto); // want to convert to generic
}
private static ICollection<Address>? ConvertDtoListToAddresses(ICollection<AddressDTO>? addressesDto)
{
if (addressesDto is not null && addressesDto.Any())
{
ICollection<Address> addys = new List<Address>();
foreach (AddressDTO dto in addressesDto)
{
// Converts from dto in constructor
addys.Add(new Address(dto));
}
return addys;
}
return null;
}
private static ICollection<Document>? ConvertDtoListToDocuments(ICollection<DocumentDTO>? documentsDto)
{
if (documentsDto is not null && documentsDto.Any())
{
ICollection<Document> docs = new List<Document>();
foreach (DocumentDTO dto in documentsDto)
{
// Converts from dto in constructor
docs.Add(new Document(dto));
}
return docs;
}
return null;
}
Here is what I tried:
Addresses = ConvertDtoListToType<Address, AddressDTO>(dto.AddressesDto);
private static ICollection<T>? ConvertDtoListToType<T, D>(ICollection<D>? dtoCollection)
{
if (dtoCollection is not null && dtoCollection.Any())
{
ICollection<T> tList = new List<T>();
foreach (D dto in dtoCollection)
{
tList.Add(new T(dto)); // <-- This is where I'm having trouble
}
return tList;
}
return null;
}
Use of a Func<D, T> factory parameter would sort this out.
private static ICollection<T>? ConvertDtoListToType<T, D>(ICollection<D>? dtoCollection, Func<D, T> factory)
{
if (dtoCollection is not null && dtoCollection.Any())
{
ICollection<T> tList = new List<T>();
foreach (D dto in dtoCollection)
{
tList.Add(factory(dto));
}
return tList;
}
return null;
}
Do keep in mind that that is almost the semantic equivalent of this:
private static ICollection<T>? ConvertDtoListToType<T, D>(ICollection<D>? dtoCollection, Func<D, T> factory)
=> dtoCollection?.Select(d => factory(d))?.ToList();
I'd question the idea that an empty dtoCollection should return a null final collection anyway. This is probably a better implementation.
So, having said that, your original method offers very little functionality benefit. It's code for code's sake. A simple Select/ToList pair keeps your code simple.
In any case, you can provide a static method off of Address and Document to provide the Func<D, T> that you need.
public class Address
{
AddressDTO dto;
public static Address CreateFromDto(AddressDTO dto)
=> new Address(dto);
public Address(AddressDTO dto)
{
this.dto = dto;
}
}
Now, calling it is like this:
var addresses = ConvertDtoListToType(addressDtos, Address.CreateFromDto);
Or:
var addresses = addressDtos?.Select(Address.CreateFromDto)?.ToList();
What you need is to be able to provide a constraint on the Type T to say that it has a constructor that takes a parameter of type D. Something like this:
private static ICollection<T>? ConvertDtoListToType<T, D>(
ICollection<D>? dtoCollection) where T : new(D)
{}
But this does not exist in C#. The workaround is to provide a factory method to create your type T given a type D. i.e. Something like:
private static ICollection<T>? ConvertDtoListToType<T, D>(
ICollection<D>? dtoCollection, Func<D, T> factory)
{
// Use factory(dto), instead of new T(dto)
}
But as #Zee says, you should have a look at Automapper, which can convert between types of collections.
Related
I am currently creating a custom way of deep-copying my objects. I use a static class for this functionality.
public static class CopyServer
{
public static int CopyDeep(int original)
{
return original;
}
//not shown: same for all other value types I use (long, float,...)
public static T CopyDeep<T>(T original) where T: ICopyAble
{
if (original == null)
return default;
if (original is ICopyAutofields)
return CopyAutofields(original);
return (T)original.CopyDeep();
}
private static T CopyAutofields<T>(T original)
{
Delegate del;
if (!_copyFunctions.TryGetValue(typeof(T), out del))
{
//not shown: Building expression for parameter etc.
foreach (var fieldInfo in typeof(T).GetFields())
{
//not shown: checking options set by custom attributes
MethodInfo methodInfo = typeof(CopyServer).GetMethod("CopyDeep", new[] { fieldInfo.FieldType });
//I can't remove the second param without getting an AmbiguousMatchException
if (methodInfo == null)
{
throw new Exception($"CopyDeep not defined for type {fieldInfo.FieldType}");
}
if (methodInfo.IsGenericMethod)
methodInfo = methodInfo.MakeGenericMethod(fieldInfo.FieldType);
Expression call = Expression.Call(methodInfo, readValue);
//not shown: Assign Expression
}
//not shown: return Expression and compiling
}
return ((Func<T, T>)del)(original);
}
}
I use T CopyAutofields<T> to build functions (by building and compiling Expression Trees) so I don't have to create copy-functions for each and every class I want to copy by hand. I control the copy-behaviour with Custom Attributes (I left this part in the code above since it is not relevant for my problem).
The code works fine as long as long as only fields with types for which a non-generic function exists are used. But it is not able to retrieve my generic function T CopyDeep<T>.
Example:
//This works:
public class Manager : ICopyAble,ICopyAutofields
{
public string FirstName;
public string LastName;
}
//This doesn't
//Meaning: typeof(CopyServer).GetMethod("copyDeep", new[] { fieldInfo.FieldType });
//in T copyAutofields<T> returns null for the Manager-field and my exception gets thrown
public class Employee : ICopyAble,ICopyAutofields
{
public string FirstName;
public string LastName;
public Manager Manager;
}
//This is what I was using before I started using the ICopyAutofields.
//This approach works, but its' too much too write since my classes usually
//have way more than three fields and I occasionally forget to update
//copyDeep()-function if I add new ones.
public class Employee : ICopyAble,ICopyAutofields
{
public string FirstName;
public string LastName;
public Manager Manager;
public IModable CopyDeep()
{
var result = new Employee();
result.FirstName = CopyServer.copyDeep(FirstName);
result.LastName= CopyServer.copyDeep(LastName);
result.Manager= CopyServer.copyDeep(Manager);
return result;
}
}
Long story short: I need a way of getting a matching function for a type T if both generic and non-generic functions with the right name exist.
In .NET 4.7.1 you need to use method GetMethods and filter the results:
class MyClass
{
public T M<T>(T t) { return default(T); }
public int M(int t) { return 0; }
}
var m = typeof(MyClass).GetMethod("M", new[] { typeof(string) }); // null
var m1 = typeof(MyClass).GetMethods()
.Where(mi => mi.Name == "M" && mi.GetGenericArguments().Any())
.First(); // returns generic method
In .NET Standard 2.1 (and .NET Core since 2.1) there is another way to resolve generic type arguments - Type.MakeGenericMethodParameter, like you can see it in this answer.
Also as workaround you can move your copyAutofields<T> method to generic class like CopyAutoFieldServer<T>:
public static class CopyAutoFieldServer<T>
{
public static T copyAutofields(T original) { ... }
}
I want to implement same simple generic conversion method but on runtime I am getting an error.
So the scenario is quite simple. I have same service that return me list of items of type External. I have my own WrapperExternal class that simply wrap this class and expose some additional functionality to it. I have some another set of classes that inherit from WrapExternal and add different functionalities.
I want to create generic method that accept list of External list items and return list of items of specified type.
My application code:
static void Main(string[] args)
{
var items = GetItemsFromServer();
var converted = ConvertItems<SubWrapperExternal>(items).ToList();
}
public static IEnumerable<T> ConvertItems<T>(IEnumerable<External> externalItems) where T : WrapperExternal
{
return externalItems
.Where( item => true)
.Select(item => (T)item);
}
When you try to run this code you will get exception in line (T)item:
An unhandled exception of type 'System.InvalidCastException' occurred in ConsoleApplication1.exe
Additional information:
Unable to cast object of type 'ConsoleApplication1.WrapperExternal' to type 'ConsoleApplication1.SubWrapperExternal'.
Do you know how can I make it to works?
Test application code:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var items = GetItemsFromServer();
var converted = ConvertItems<SubWrapperExternal>(items).ToList();
}
private static List<External> GetItemsFromServer()
{
return new List<External>
{
new External{Name = "A"},
new External{Name = "B"},
new External{Name = "C"},
};
}
public static IEnumerable<T> ConvertItems<T>(IEnumerable<External> externalItems) where T : WrapperExternal
{
return externalItems
.Where( item => true)
.Select(item => (T)item);
}
}
class External
{
public string Name { get; set; }
}
class WrapperExternal
{
public External External { get; private set; }
public WrapperExternal(External external)
{
External = external;
}
public static explicit operator WrapperExternal(External item)
{
return item != null ? new WrapperExternal(item) : null;
}
public static implicit operator External(WrapperExternal item)
{
return item != null ? item.External : null;
}
}
class SubWrapperExternal : WrapperExternal
{
public SubWrapperExternal(External external)
: base(external)
{
}
public static explicit operator SubWrapperExternal(External item)
{
return item != null ? new SubWrapperExternal(item) : null;
}
public static implicit operator External(SubWrapperExternal item)
{
return item != null ? item.External : null;
}
}
}
Conversion operators are a faff to use with generics - generics don't support any static operator overloads. Because of this, the (T) cast is performing a non-converting type check (generics need to use the same IL for every possible T, remember) - a basic castclass.
The only "simple" way of doing what you want is to cheat with dynamic:
return externalItems.Select(item => (T)(dynamic)item);
Since the C#-specific dynamic provider knows all the common rules of C#, it knows about conversion operators, and will apply them on-demand. There is a slight performance cost associated with this, but it isn't as bad as it looks at first glance, as the strategy is cached (as IL) once per type - it doesn't perform per-item reflection.
I have a method that has a signature that looks like this:
public IList<T> GetReferenceData<T>(TransactionManager transactionManager = null)
{
IList<T> collection;
var cacheData = DataCacheManager.Instance.GetCacheItem(typeof(T).Name);
if (cacheData != null)
{
collection = (IList<T>)cacheData;
}
else
{
collection = this.GetReferenceDataNoCache<T>(transactionManager);
DataCacheManager.Instance.AddCacheItem(typeof(T).Name, collection);
}
return collection;
}
I have another method that allows me to pass in a string, which converts that string to the appropriate type. I then want to call the above method.
public IList GetReferenceDataByType(string referenceType)
{
// this works and returns the appropriate type correctly
var type = this.GetEntity(referenceType);
// now I'm stuck
return this.GetReferenceData<?>();
}
What replaces the question mark?
If I understand your question correctly you want somethiong like this:
public IList GetReferenceDataByType(string referenceType)
{
// this works and returns the appropriate type correctly
var type = this.GetEntity(referenceType);
var method = this.GetType().GetMethod("GetReferenceData");
var generic = method.MakeGenericMethod(type);
return (IList) generic.Invoke(this, new object[] { null });
}
Note that IList<T> does not implement IList so that cast may fail.
Looks like you should invert your approach.
Instead of calling generic methods from non-generic alternatives, your GetReferenceData and GetReferenceDataNoCache should be rewritten as non-generic ones:
public IList GetReferenceData(Type type, TransactionManager transactionManager = null)
{
// ...
}
private IList GetReferenceDataNoCache(Type type, TransactionManager transactionManager = null)
{
// ...
}
public IList<T> GetReferenceData<T>(TransactionManager transactionManager = null)
{
return (IList<T>)GetReferenceData(typeof(T), transactionManager);
}
Look at your code: the only benefit from T in GetReferenceData<T> is typeof(T).
The rest of method is non-generic in fact.
Visit these websites
http://predicatet.blogspot.com/2009/04/c-string-to-generic-type-conversion.html!
Universal generic type conversion from string
Convert string to generic type (basic or array) in C#
i hope it will help you
Currently we implement a mapping service like this (the service uses automapper, and we make use of the projection feature on it for this part)
// Injected
// IGenericRepository<Entity> entityRepo
var query = this.entityRepo
.FindAll(a => a.Id == someId)
.Take(1);
var result = this.mappingService
.Map<Entity, EntityDto>(query)
.FirstOrDefault();
I'd like to create an extension that would allow me to do the following
var result = this.entityRepo
.FindAll(a => a.Id == someId)
.Take(1).Map<EntityDto>() <--- Entity inferred from repo type
.FirstOrDefault();
My current attempt:
public static class IQueryableExtensions
{
private static IMappingService mappingService;
// will need to be called in app initialization
public static void InitialiseMapper(IMappingService service)
{
mappingService = service;
}
public static IEnumerable<TDto> Map<TAttribute, TDto>(this IQueryable<TAttribute> value)
where TDto : class
where TAttribute : IEntity
{
return mappingService.Map<TAttribute, TDto>(value);
}
}
Thus currently my implementation would look like this.
var result = this.entityRepo
.FindAll(a => a.Id == someId)
.Take(1).Map<Entity,EntityDto>()
.FirstOrDefault();
Questions:
1) How would i go about inferring the entity type from the IQueryable object
2) I realize i cant create a constructor that takes parameters, when creating a static class. Is the way i init the mapper the best/only way?
I tried that with reflection. The constraints are only for demo. If you want to call the reflection code multiple times be sure to cache the final methodinfo.
void Main()
{
var a = new Entity[] {new Entity { name = "a"},new Entity { name = "b"}};
Console.WriteLine(a.Take(1).Map<EntityDto>());
}
public class Entity
{
public string name;
}
public class EntityDto
{
public string dtoname;
}
public static class EntityExtensions
{
public static IEnumerable<U> Map<T,U>(this IEnumerable<T> e) where T: Entity where U: EntityDto, new()
{
foreach(var a in e)
{
yield return new U() { dtoname = a.name };
}
}
public static IEnumerable<U> Map<U>(this IEnumerable<object> e)
{
var method = typeof(EntityExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m => m.Name == "Map" && m.GetGenericArguments().Length == 2)
.Single();
method = method.MakeGenericMethod(e.GetType().GetGenericArguments()[0], typeof(U));
return method.Invoke(null, new object[] { e}) as IEnumerable<U>;
}
}
1) Currently, you simply can't do that in C#. The type inference is not good enough. You can either specify all type parameters or none of them.
Edit: If you really want the version with a single parameter, you have to delete the second type parameter, type the parameter as non-generic IQueryable and deal with it. One way of doing that would be to determine the generic IQueryable<T> type at runtime. However, this requires reflection. In the case of IQueryable, you can also use the query provider to get around the reflection.
2) You can use a static type constructor.
public static class MyExtensions {
static MyExtensions() {
//initialization goes here
}
}
This type constructor is even called thread-safe. However, if you manage to throw an exception here, you cannot access the MyExtensionsclass!
I have to create a list, but i only know the class name
public void getList(string className)
{
IList lsPersons = (IList)Activator.CreateInstance(
typeof(List<>).MakeGenericType(Type.GetType(className))));
}
I tried so many methods but nothing works for me.
You can make a generic list but it is not useful. If you want to have a generic List<T> , somewhere you SHOULD incorporate your prior knowledge about the demanded type. For example you can do something like this:
if(className == "Employee") // this is where your prior knowledge is playing role
{
IList<Employee> lsPersons = (IList<Employee>)Activator.CreateInstance(
typeof(List<Employee>).MakeGenericType(Type.GetType(className))));
}
Also, you can make a generic list of any type via something like this:
public static class GenericListBuilder
{
public static object Build(Type type)
{
var obj = typeof(GenericListBuilder)
.GetMethod("MakeGenList", BindingFlags.Static|BindingFlags.NonPublic)
.MakeGenericMethod(new Type[] { type })
.Invoke(null, (new object[] {}));
return obj;
}
private static List<T> MakeGenList<T>()
{
return new List<T>();
}
}
And it is possible to consume it like:
var List<Employee> = GenericListBuilder.Build(typeof(Employee)) as List<Employee>;
or
IList list = GenericListBuilder.Build(Type.GetType(className)) as IList;
The last line is completely blind and I think it is very close to what you have in mind. But does it have any benefit? I don't think.