views:

236

answers:

4

Hi, How do you think the following code? Is this good? If so, why it is? If not, why it is not? And how CLR see this code?

public abstract class EntityBase<TEntity> : IEquatable<TEntity>
{        
    public bool Equals(TEntity other)
    {
        // check equalitiy
    }

    // yes, below is object's Equals and GetHashCode method implementation
}

public class Person : EntityBase<Person>
{
}

I have a bit odd feeling on this. Like chicken and egg problem. And here is .Net framework code which has the same behaviour.

public sealed class String : IComparable<string>, IEquatable<string> // I removed other interfaces

Any thoughts?

+2  A: 

In the right circumstances (e.g. implementing IComparable<T>) it is exactly the right thing to do.

But this can only be determined on a case by case basis, looking at the details of why it is being considered.

On the other hand, C++ allows the "curiously recurring base pattern":

template<typename T>
class SomeWrapper<T> : T { ... ]

where a generic class inherits its generic wrapper. This allows some advanced wrapping scenarios, but can rapidely become confusing if used beyond wrapping. Fortunately(?) this pattern is not allowed in .NET.

Richard
@Richard: Yes. Case by case basis. But, how do think on my case? :)
Soe Moe
@Soe: Not enough detail. Need to know what `Entity<T>` does with `T` (that it can't do with `this.GetType()`).
Richard
@Richard: Thanks for your reply. I want to implement IEquatable<T> for every entity class. (e.g Person) but I don't want to repeat.
Soe Moe
A: 

I don't see a problem with your code. Why would it be a problem?

About the internal representation, the dotNet JIT compiles a class for every generic version. So the following:

class Foo<T> { public T Property; }
Foo<Int> fooint;
Foo<String> foostring;

Is sort of compiled into:

class FooInt { public Int Property; } 
class FooString { public String Property; }
FooInt fooint;
FooString foostring;
// This is kept for if it is needed later.
// For example for generic casting, a C#4 feature.
class Foo<T> { public T Property; }
Dykam
The interesting part is class Person : EntityBase<Person>. That's why he said it's like chicken and egg problem.
Jimmy Chandra
Ah, it is probably me, I make absurd constructs all the time, so I don't see this as special :P.
Dykam
The jitter does _not_compile a new one for every version. The jitter compiles a new one for every version with a different type argument _only_ if the type argument is a value type.
Eric Lippert
Hmm, really? But doesn't that mean an expensive cast is done every time you call a Ref type? Or isn't that the case as only the pointer size matters.
Dykam
Why would an "expensive cast" be done? Can you provide an example of where you think the jitter would have to generate an "expensive cast"? (FYI, there are cases where we have to generate expensive and unnecessary casts, but they are extremely rare corner cases that do not usually crop up in real-world code. I suspect that you are thinking of some operation that looks expensive but is actually cheap.)
Eric Lippert
+1  A: 

Not really an answer, but I found it sort of make sense after I wrote this weird code...

class Program
{
    static void Main(string[] args)
    {
        var wife = new Human(Gender.Female);
        var baby = wife.GiveBirth();
        Console.WriteLine(baby.Gender);

        Console.ReadKey();
    }
}

class CanGiveBirthTo<T> where T : new()
{
    public CanGiveBirthTo()
    {
    }

    public T GiveBirth()
    {
        return new T();
    }
}

class Human : CanGiveBirthTo<Human>
{
    public Gender Gender { get; private set; }

    public Human(Gender gender)
    {
        Gender = gender;
    }

    public Human()
    {
        Gender = RandomlyAssignAGender();
    }

    Gender RandomlyAssignAGender()
    {
        var rand = new Random();
        return (Gender) rand.Next(2);
    }
}

enum Gender
{
    Male = 0,
    Female = 1
}
Jimmy Chandra
A: 

For other people reference, I copied the answer of Eric Lippert (who answer in the comment).

Though the definition appears circular, it is not. However, the C# compilers cycle detection algorithm is both wrong and weak. Wrong because it incorrectly detects non-cycles as cycles, and weak because it fails to detect certain very nasty cycles. If this topic interests you, see my article on it here: http://blogs.msdn.com/ericlippert/archive/2008/05/07/covariance-and-contravariance-part-twelve-to-infinity-but-not-beyond.aspx

Soe Moe