简体   繁体   中英

What does this parameter type constraint mean?

I am looking at some code and I don't understand what a particular constraint means in the following class definition:

internal abstract class Entity<T> : Entity
    where T : Entity<T>
{ ... }

I don't understand what this implies about parameter type T .

This is similar to the " Curiously Recurring Template Pattern " (but it is NOT the same).

It can be used (among other things) to help constraint the parameter types of methods in a derived class to the same type as the derived class itself.

Here's an interesting blog post from Eric Lippert on this subject .

The main use of this is to force classes that derive from Entity<T> to implement some method that accepts a parameter of the same type as the deriving class.

In the following code sample, we declare in the Entity<T> class a method DoSomethingWithTheSameTypeAsMe() which accepts a parameter of type T .

Because of the generic constraint, this will force any classes that derive from Entity<T> to implement a version of DoSomethingWithTheSameTypeAsMe() which takes a parameter of the deriving class type.

This is of limited use, and it is very confusing to read, so I agree with Eric Lippert when he says you should avoid such code!

using System;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main()
        {
            var test1 = new Derived1();
            var test2 = new Derived2();

            test1.DoSomethingWithTheSameTypeAsMe(test1);
            test2.DoSomethingWithTheSameTypeAsMe(test2);
        }
    }

    public class Entity
    {
        public string Hello()
        {
            return "Hello, World.";
        }
    }

    public abstract class Entity<T>: Entity where T: Entity<T>
    {
        public abstract void DoSomethingWithTheSameTypeAsMe(T item);
    }

    public sealed class Derived1: Entity<Derived1>
    {
        // You are forced to implement DoSomethingWithTheSameTypeAsMe() with a param type "Derived1".
        // (i.e. the parameter is the same type as 'this')

        public override void DoSomethingWithTheSameTypeAsMe(Derived1 item)
        {
            Console.WriteLine("Doing something with a Derived1 item: " + item.Hello());
        }
    }

    public sealed class Derived2: Entity<Derived2>
    {
        public override void DoSomethingWithTheSameTypeAsMe(Derived2 item)
        {
            Console.WriteLine("Doing something with a Derived2 item: " + item.Hello());
        }
    }
}

Although I commented, I'm going to stick my oar in because I want to also note on what the base type gets from this.

Simply: T must inherit Entity<T> .

It's a kind of self-referencing generic often used such that the base class can contain the derived class type (via T ) in methods and other areas. It simply avoids you having to cast stuff or use a base reference in derived types. It can be quite useful, though I rarely see it used in our code.

I will note that this does not mean the base class can suddenly access derived members. It can still only see the lowest known type defined by the constraints, if they exist. If no constraints exist, object is the lowest known type. The benefit is from the perspective of the derived type and the cleanliness it grants on code that is pushed into a base class.

In your case, it would see Entity<T> and Entity members. This is the reason for constraints.

A standard usage would be something like:

public class Customer : Entity<Customer>
{
}

public abstract class Entity<T> 
    where T : Entity<T>
{
    public T Clone(T entityToClone)
    {
        return default(T); // Clone code here, returns derived type.
    }
}   


// Grants you...
Customer clonedCustomer = currentCustomer.Clone();

// Instead of...
Customer clonedCustomer = (Customer)currentCustomer.Clone();

// Ignore ethical constraints on cloning customers and definitely do not tell your sales team that you can ;-)

It says that T must be of type Entity<T> or derives from that type

Though it seems paradoxical it's valid and can sometimes be useful too, though the cases are rare and often can be handled in different easier to undertand ways.

It's often refered to in C++ lingo as the Curiously recurring template pattern

In C# the capabilities are somewhat more restricted than when using the pattern in C++ The concrete classes of this patter will typically look like this

class MyClass<ItemType> : Entity<MyClass<ItemType>> {
  //...
}

or simply

class MyClass : Entity<MyClass> {
   //...
}

one example of when this might be useful is when working with attributes on the type.

Say you are creating a list of widgets at run time. The list includes all types that derives from Entity<T> and you populate the information based on meta data from attributes. In Entity<T> you can handle this once and for all

void RegisterWidget(){
  var attributes = typeof(T).GetAttributes();
  //do what ever you need to
}

this would of course work with out the constraint but it might still make sense from a functional perspective or to show intend and it might be needed in other parts of the code

It says that T must be or inherit from Entity<T> , which is what has the T that you're restricting. Obviously T can't be an Entity<T> because that's abstract, so it must be something that inherits from it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM