Generic class with self-referencing type constraint

后端 未结 3 973
感动是毒
感动是毒 2020-12-05 10:51

Consider the following code:

abstract class Foo
    where T : Foo, new()
{
    void Test()
    {
        if(Bar != null)
            Bar(th         


        
相关标签:
3条回答
  • 2020-12-05 11:37

    You can cast 'this' to T:

    Bar((T)this);
    

    This however will fail if you have the following:

    public class MyFoo : Foo<MyFoo> { }
    
    public class MyOtherFoo : Foo<MyFoo> { }
    

    Because 'MyOtherFoo' is not an instance of 'MyFoo'. Take a look at this post by Eric Lippert, one of the designers of C#.

    0 讨论(0)
  • 2020-12-05 11:45

    The code would be clearer if you didn't use "Bar" for two purposes. That having been said, I think what's needed is to use a generic with two parameters (e.g. T and U) such that T derives from U, and U derives from Foo. Alternatively, it's possible to do some nice things with interfaces. A useful pattern is to define:

    interface ISelf<out T> {T Self<T> {get;}}
    

    and then, for various interfaces that one might want to combine in an object:

    interface IThis<out T> : IThis, ISelf<T> {}
    interface IThat<out T> : IThat, ISelf<T> {}
    interface ITheOtherThing<out T> : ITheOtherThing, ISelf<T> {}
    

    If classes that implement IThis, IThat, and ITheOtherThing also implement ISelf<theirOwnTypes>, one can then have a routine whose parameter (e.g. "foo") has to implement both IThis and IThat accept the parameter as type IThis. Parameter "foo" will be of type IThis (which in turn implements IThis) while Foo.Self will be of type IThat. Note that if things are implemented this way, one may freely typecast variables to any desired combination of interfaces. For example, in the above example, if the object passed as "foo" was a type which implemented IThis, IThat, ITheOtherThing, and ISelf<itsOwnType> it could be typecast to ITheOtherThing>, or IThis, or any other desired combination and arrangement of those interfaces.

    Really a pretty versatile trick.

    Edit/Addendum

    Here's a somewhat more complete example.

    namespace ISelfTester
    {
        interface ISelf<out T> {T Self {get;} }
    
        interface IThis { void doThis(); }
        interface IThat { void doThat(); }
        interface IOther { void doOther(); }
    
        interface IThis<out T> : IThis, ISelf<T> {}
        interface IThat<out T> : IThat, ISelf<T> {}
        interface IOther<out T> : IOther, ISelf<T> {}
    
        class ThisOrThat : IThis<ThisOrThat>, IThat<ThisOrThat>
        {
            public ThisOrThat Self { get { return this; } }
            public void doThis() { Console.WriteLine("{0}.doThis", this.GetType()); }
            public void doThat() { Console.WriteLine("{0}.doThat", this.GetType()); }
        }
        class ThisOrOther : IThis<ThisOrOther>, IOther<ThisOrOther>
        {
            public ThisOrOther Self { get { return this; } }
            public void doThis() { Console.WriteLine("{0}.doThis", this.GetType()); }
            public void doOther() { Console.WriteLine("{0}.doOther", this.GetType()); }
        }
        class ThatOrOther : IThat<ThatOrOther>, IOther<ThatOrOther>
        {
            public ThatOrOther Self { get { return this; } }
            public void doThat() { Console.WriteLine("{0}.doThat", this.GetType()); }
            public void doOther() { Console.WriteLine("{0}.doOther", this.GetType()); }
        }
        class ThisThatOrOther : IThis<ThisThatOrOther>,IThat<ThisThatOrOther>, IOther<ThisThatOrOther>
        {
            public ThisThatOrOther Self { get { return this; } }
            public void doThis() { Console.WriteLine("{0}.doThis", this.GetType()); }
            public void doThat() { Console.WriteLine("{0}.doThat", this.GetType()); }
            public void doOther() { Console.WriteLine("{0}.doOther", this.GetType()); }
        }
        static class ISelfTest
        {
            static void TestThisOrThat(IThis<IThat> param)
            {
                param.doThis();
                param.Self.doThat();
            }
            static void TestThisOrOther(IThis<IOther> param)
            {
                param.doThis();
                param.Self.doOther();
            }
            static void TestThatOrOther(IThat<IOther> param)
            {
                param.doThat();
                param.Self.doOther();
            }
    
            public static void test()
            {
                IThis<IThat> ThisOrThat1 = new ThisOrThat();
                IThat<IThis> ThisOrThat2 = new ThisOrThat();
                IThis<IOther> ThisOrOther1 = new ThisOrOther();
                IOther<IThat> OtherOrThat1 = new ThatOrOther();
                IThis<IThat<IOther>> ThisThatOrOther1 = new ThisThatOrOther();
                IOther<IThat<IThis>> ThisThatOrOther2a = new ThisThatOrOther();
                var ThisThatOrOther2b = (IOther<IThis<IThat>>)ThisThatOrOther1;
                TestThisOrThat(ThisOrThat1);
                TestThisOrThat((IThis<IThat>)ThisOrThat2);
                TestThisOrThat((IThis<IThat>)ThisThatOrOther1);
                TestThisOrOther(ThisOrOther1);
                TestThisOrOther((IThis<IOther>)ThisThatOrOther1);
                TestThatOrOther((IThat<IOther>)OtherOrThat1);
                TestThatOrOther((IThat<IOther>)ThisThatOrOther1);
            }
        }
    }
    

    The thing to note is that some classes implement different combinations of IThis, IThat, and IOther, and some methods require different combinations. The four non-static classes given above are all unrelated, as are the interfaces IThis, IThat, and IOther. Nonetheless, it is possible for method parameters to require any combination of the interfaces provided that implementing classes follow the indicated pattern. Storage locations of a "combined" interface type may only be passed to parameters which specify the included interfaces in the same order. An instance of any type which properly implements the pattern, however, may be typecast to any "combined" interface type using any subset of its interfaces in any order (with or without duplicates). When used with instances of classes that properly implement the pattern, the typecasts will always succeed at run-time (they could fail with rogue implementations).

    0 讨论(0)
  • 2020-12-05 11:51
    delegate void Bar<T>(Foo<T> foo) where T : Foo<T>, new();
    

    It works great. I tested it.

    here is the test code

    public abstract class Foo<T> where T :Foo<T> {
        public event Bar<T> Bar;
    
        public void Test ()
        {
            if (Bar != null)
            {
                Bar (this);
            }
        }
    }
    
    public class FooWorld : Foo<FooWorld> {
    }
    
    public delegate void Bar<T>(Foo<T> foo) where T : Foo<T>;
    
    class MainClass
    {
        public static void Main (string[] args)
        {
            FooWorld fw = new FooWorld ();
            fw.Bar += delegate(Foo<FooWorld> foo) {
                Console.WriteLine ("Bar response to {0}", foo);
            };
    
            fw.Test ();
        }
    }
    
    0 讨论(0)
提交回复
热议问题