Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
237 views
in Technique[技术] by (71.8m points)

c# - Not getting properties on generic parameter T when trying to create generic comparer to compare 2 objects

I want to compare 2 objects like below :

  • I don't want 2 employees in the same department
  • I don't want 2 Animals in the same Zoo
  • So on.....

I am trying to implement IEqualityComparer to accept generic type argument to take Employee or Animals or any object and to the comparison for me but I ran into a problem because since I have T as an argument, I am not getting the properties on T.

public class GenericComparer<T> : IEqualityComparer<T>
    {
        public bool Equals(T x, T y)
        {
        // Not getting a property here T.??
            //now if I would have T as an Employee then I could do something like this here:
       // return String.Equals(x.Name, y.Name); // but this will work because I know that Name is string 
     // but lets say if I have a different datatype that I want to compare then how can i do that?
        }

        public int GetHashCode(T obj)
        {
            return obj.
        }
    }

The thing is I don't want to create 2 classes like EmployeeComparer: IEqualityComparer<Employee> and AnimalComparer : IEqualityComparer<Animal> as the code will be somewhat similar.

Is it possible to create generic compared to compare any objects of the same types?

Update:

I am just trying to understand the limitations of generics with reference types. When should we create a Generic class or methods to accept reference types and when we should not.

I was thinking that since List<T> can accept anything like List<Employee> or List<Animal> or anything then why my GenericComparer cannot.

Because when we create List<Employee> then we can run for each loop and access the properties right:

foreach(var employee in employees)
{
  string name = employee.Name; //Here we are able to access the properties
}

Then how come not in my case?

Answer to all the above questions will help me understand generics better with reference type especially. So, if someone can provide an answer to all this question and why it is not possible with my GenericComaprer<T> and how it is possible with List<T> then I will really appreciate :)

question from:https://stackoverflow.com/questions/66050166/not-getting-properties-on-generic-parameter-t-when-trying-to-create-generic-comp

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

when we create List<Employee> then we can run for each loop and access the properties

Sure, because you are using List<T> by providing it the concrete type (Employee) to use in place for T. But if you were to look at the internals of (or try to write your own version of) List<T> you'd see you have no way to access an hypothetical Name property of T. That is because C# is a strictly typed language (contrarily to Javascript or Python for example). It won't let you use anything it doesn't know beforehand and for sure exists.

The way you defined your GenericComparer<T> class, all the compiler know is that T can do what every C# objects can, that is... not much (you can use ToString() and GetType() and compare references, that's pretty much all).

Then, because otherwise generics wouldn't be very useful, you can specify constraints on your generic type: you can, for example, tell the compiler that T must implement some interface. If you do this, then you can access any member defined in this interface on your T instances from inside the generic class.

For the sake of the example, let's say you have employees and animals; both employees and animals have a Name, then employees also have a Salary but animals have a Species. This could look like this:

public class Employee {
    public string Name { get; set; }
    public double Salary { get; set; }
}

public class Animal {
    public string Name { get; set; }
    public string Species { get; set; }
}

If we keep it that way, as explained above, you won't be able to access the properties from your generic class. So let's introduce an interface:

public interface IHasName {
    string Name { get; }
}

And add : IHasName at the end of the Employee and Animal class declarations.

We also need to tweak GenericComparer declaration this way:

public class GenericComparer<T> : IEqualityComparer<T> where T : IHasName

By doing this, we tell the compiler that the T in GenericComparer must implement IHasName. The benefit is now you can access the Name property from within GenericComparer. On the other side, you won't be able to use GenericComparer by passing it anything that doesn't implement IHasName. Also note that the interface only defines the ability to get the Name property, not to set it. So, although you can of course set the Name on Employee and Animal instances, you won't be able to do so from inside GenericComparer.

With the definitions above, here is an example of how you could write the GenericComparer<T>.Equals method:

public bool Equals(T x, T y)
{
    // Just a convention: if any of x or y is null, let's say they are not equal
    if (x == null || y == null) return false;

    // Another convention: let's say an animal and an employee cannot be equal
    // even if they have the same name
    if (x.GetType() != y.GetType()) return false;

    // And now if 2 employees or animals have the same name, we'll say they are equal
    if (x.Name == y.Name) return true;

    return false;
}

All that said, I don't know exactly what your use case for GenericComparer<T> is (maybe you need this in a Dictionary or some other method that requires a way to compare instances... Anyway, as other commenters have stated, the proper way to go would probably be to:

  • override Equals and GetHashCode
  • provide == and != operators
  • implement IEquatable<T>
  • Do this on all classes you want to have a custom implementation of equality.

If you happen to use a recent version of Visual Studio (I'm using VS 2019 16.8 at the moment), you can automatically do all of this by right-clicking the class name, choosing Quick Actions and Refactorings > Generate Equals and GetHashCode. You will be presented with a window that allows you to choose the properties that should be part of the equality logic and you can also optionally choose to implement IEquatable<T> and generate == and !=. Once done, I'd recommend you review the generated code and make sure you understand what it does.

By the way, if you do so, you'll notice that the generated code uses EqualityComparer<Employee>.Default. This is pretty much what you tried to implement by yourself with your GenericEqualityComparer.

Now to the part of your questions relating to reference types. I'm not sure I understand your questions for I don't see an obvious link between generic types and references. Generic types can act just as well on both reference and value types.

What bothers you is maybe the fact that equality does not work the same way in reference and value types:

  • For reference types, the default way the compiler considers things equal is to look at their references. If the references are the same, then the things are considered the same. To put it differently suppose you create 2 instances of an Employe class and feed them with exactly the same Name and Salary. Because they are distinct objects (with distinct places in memory, that is different references), emp1 == emp2 will return false.
  • In the case of value types (suppose Employee is a struct and not anymore a class), the compiler does something else: it compares all the properties of the struct and decides based upon their content whether the 2 employees are equal or not. In this case, emp1 == emp2 will return true. Note that here, the compiler (or rather the .NET runtime) is doing something similar to what you attempt to do with your universal comparer. However, it only does so for value types and it is rather slow (this is why one should often implement IEquatable and override Equals and GetHashcode on structures).

Well, I'm not sure I've answered all your questions, but if you want to know more, you should definitely go through some C# tutorials or documentation to understand more about reference vs value types and equality (even before you jump into implementing your own generic types).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...