Search code examples
c#genericsmappingautomapperdto

Converting c# methods to a generic method


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;
}

Solution

  • 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();