0

Consider the following code:

public interface IIdentifiable<T>
{
    T Id { get; set; }
}

public interface IViewModel
{
}

public class MyViewModel1 : IViewModel, IIdentifiable<int>
{
    public string MyProperty { get; set; }

    public int Id { get; set; }
}

public class MyViewModel2 : IViewModel, IIdentifiable<string>
{
    public string MyProperty { get; set; }

    public string Id { get; set; }
}

I also have class that operates with ViewModels:

public class Loader<T> where T: IViewModel
{
    public void LoadData()
    {
        /*some important stuff here*/

        if (typeof(IIdentifiable<??>).IsAssignableFrom(typeof(T)))
        {                     // ^- here's the first problem
            data = data.Where(d => _dataSource.All(ds => ((IIdentifiable<??>) ds).Id != ((IIdentifiable<??>) d).Id)).ToList();
        }                                                             // ^---- and there the second ----^

        /*some important stuff here too*/
    }
}

Now, as you can see, viewmodels that I have might implement the IIdentifiable<> interface. I want to check that, and if it's true, I want to make sure my data list does not contains any entry that are already present in my _dataSourse list.

So I have 2 questions:

  1. I don't know what IIdentifiable<> has in its generic parentheses, it might be int, string or even GUID. I tried typeof(IIdentifiable<>).IsAssignableFrom(typeof(T)) which is the correct syntax, yet it always returns false. Is there a way to check whether T is IIdentifiable<> without knowing the exact generic type?

  2. If there is an answer for the first question, I would also like to know how can I compare the Id fields without knowing their type. I found this answer quite useful, yet it doesn't cover my specific case.

I know that I probably can solve that problem if I make my Loader<T> class a generic for two types Loader<T,K>, where K would be the type in IIdentifiable<>, yet I would like to know if there are other solutions.


P.S. In addition to my first question: I'm also curious why one can write something like this typeof(IIdentifiable<>).IsAssignableFrom(typeof(T)) if it returns false when the generic type of IIdentifiable<> is not specified?


Edit: I guess, in hindsight, I understand why I can't write the code this bluntly - because there's might be the collection ICollection<IViewModel> where the entries implement different types of IIdentifiable<> (or don't implement it at all), and the check like that would fail awkwardly. Yet maybe there is a way to do something like that with some restrictions, but without creating second generic parameter to my Loader?

Dmitry Volkov
  • 1,347
  • 1
  • 18
  • 33
  • 1
    Im not sure of what are you trying to achieve but maybe would help if your IIdentifiable implements IComparable – ogomrub Jul 25 '17 at 10:38

3 Answers3

1

Try add two methods to your Loader<T>:

public bool CanCast<TId>()
{
    var identifiableT = typeof(IIdentifiable<>).MakeGenericType(typeof(TId));
    return identifiableT.IsAssignableFrom(typeof(T));
}

public IEnumerable<IIdentifiable<TId>> Filter<TId>(IEnumerable<T> data)
{
    return data.Where(d => _dataSource.All(
      ds => !((IIdentifiable<TId>) ds).Id.Equals(((IIdentifiable<TId>) d).Id)));
}

Then in LoadData

if (CanCast<int>())
    data = Filter<int>(data);
else if (CanCast<Guid>())
    data = Filter<Guid>(data);
// and so om
Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37
  • Thanks for the answer, Aleks, thumbs up! Though I can't say it satisfies my needs, for I didn't want to introduce the second generic argument, and I want to go through enumeration of all possible cases even less. – Dmitry Volkov Jul 26 '17 at 05:18
1

Well, I would suggest you to always use a string for identification. You can convert int and guid to a string. And if you want to ensure proper type is used then you can prefix the string with type information.

However, I do think that the performance of you algorithm would be very poor as you wouls essentially loop 2 containers so it would be O(n * m).

Thus it would be best to either do appropriate SQL query if both sources are from the database or use a dictionary if you do it in code. Alternatively if data is properly sorted, you could find duplicates more efficiently.

By the way generics are quite limited in C#. Sometime using ˋFunc<>ˋ could help but even then you have to provide extra information to the algorithm.

Phil1970
  • 2,605
  • 2
  • 14
  • 15
1

We should address your question in two steps (because there really are two problems to solve here).

First, make following change to your interface IIdentifiable<T>

public interface IIdentifiable<T>
    where T : IEquatable<T>
{
    T Id { get; set; }
}

This will ensure that you can compare Id properties correctly.

Secondly, in your LoadData() method, change the if statement to

    if (T is IIdentifiable<T>)
    {                     // ^- here's the first problem
        data = data.Where(d => _dataSource.All(ds => ((IIdentifiable<T) ds).Id != ((IIdentifiable<T) d).Id)).ToList();
    }
Tanveer Badar
  • 5,438
  • 2
  • 27
  • 32