Ctrl_Alt_Defeat
Ctrl_Alt_Defeat

Reputation: 4009

Write similar logic using generics

I have the following method which determines which cars I need to delete from the DB.

private List<CarDTO> BuildCarsToDelete(IList<CarDTO> newCars, IList<CarDTO> existingCars)
{
    var missingCars = new List<CarDTO>();

    var cars = newCars.Select(c => c.CarId);
    var newCarIds = new HashSet<int>(cars);

    foreach (var car in existingCars)
    {
        //If there are no new cars then it had some and they have been removed
        if (newCars.Count() == 0)
        {
            missingCars.Add(car);
        }
        else
        {
            if (!newCarIds.Contains(car.CarId))
            {
                missingCars.Add(car);
            }
        }
    }

    return missingCars;
}

This works as I want - but if I want to achieve the same functionality for Customers or Apartments of other DTOs I will be copying a pasting the code but only changing the variable names and the Type of DTO around - is there a nicer way possible using generics which would keep the algorithm and logic as it is but allow me to use on any DTO?

Upvotes: 2

Views: 94

Answers (3)

juharr
juharr

Reputation: 32276

If all the ids are of type int then you can do that by passing in a Func to determine the id.

private List<T> BuildToDelete<T>(
    IList<T> newItems, 
    IList<T> existingItems, 
    Func<T, int> getId)
{
    var missingItems = new List<T>();

    var items = newItems.Select(getId);
    var newItemIds = new HashSet<int>(items);

    foreach (var item in existingItems)
    {
        if (newItems.Count() == 0)
        {
            missingItems.Add(item);
        }
        else
        {
            if (!newItemIds.Contains(getId(item)))     
            {
                missingItems.Add(item);
            }
        }
    }

    return missingItems;
}

Then call as shown below:

var results = BuildToDelete(newCars, existingCars, c => c.CarId);

Upvotes: 5

apocalypse
apocalypse

Reputation: 5884

Try something cleaner:

1) create flexible equality comparer (need to add some null checking etc.)

public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> comparer;
    Func<T, int> hash;

    public FuncEqualityComparer (Func<T, T, bool> comparer, Func<T, int> hash)
    {
        this.comparer = comparer;
        this.hash = hash;
    }

    public bool Equals (T x, T y) => comparer (x, y);

    public int GetHashCode (T obj) => hash (obj);
}

2) and now, just simply:

var carComparerByID = new FuncEqualityComparer<CarDTO> ((a, b) => a.CarId == b.CarId, x => x.CarId.GetHashCode ());

var result = existingCars.Except (newCars, carComparerByID).ToList ();

Upvotes: 0

Bradley Uffner
Bradley Uffner

Reputation: 16991

Assuming you use the interface approach mentioned in comments, a generic version could look something like this:

private List<TEntity> BuildEntitiesToDelete(IList<TEntity> newEntities, IList<TEntity> existingEntities) where TEntity : IEntityWithId
{
    var missingEntities = new List<TEntity>();

    var entities = newEntities.Select(e => e.Id);
    var newEntityIds = new HashSet<int>(entities);

    foreach (var entity in existingEntities)
    {
        if (entities.Count() == 0)
        {
            missingEntities.Add(entity);
        }
        else
        {
            if (!newEntityIds.Contains(entity.Id))
            {
                missingEntities.Add(entity);
            }
        }
    }

    return missingEntities;
}

IEntityWithId is probably a poor name for the interface, but I'll leave picking a better name up to you.

Upvotes: 3

Related Questions