Answered Strange Behavior with Generics:

  • Thursday, May 01, 2008 4:58 AM
     
     

     

    When I define a Generic at class level with a constraint, I get a compile exception , however , when I implement Generic at method level with the same constraint , all goes well ...
    Wondering , wether its a bug in generics or the bug lies in my way of interpretation :-(

    Have Tried to explain the problem below  :

    Case 1:
    I have defined a abstract generic class and defined the generic type at class level , with a constraint . Below is the class definition.

    public abstract class BaseVOMapper<T> where T:BaseVO
        {
            public abstract void Insert(BaseVO child , BaseVO parent );

            public abstract void SaveChildrens(List<T> childListToPersist, BaseVO parentVO);
        }

    Below is the implementation o f the derived class , where FlightList is a List<FlightVO> & FlightVO: BaseVO  & as per polymorphism ,I assume , I can pass List<FlightVO> where List<BaseVO> is expected.However , I endup getting the below error.
    Argument '1': cannot convert from 'System.Collections.Generic.List<ValueObjects.FlightVO>' to 'System.Collections.Generic.List<ValueObjects.BaseVO>'

    i.e
     public class FlightVO : BaseVO
        {
            public int FlightId;
            public string FlightName;
        }


        public class ScheduleVOMapper: BaseVOMapper<BaseVO>
        {
            public override void Insert(ValueObjects.BaseVO child, ValueObjects.BaseVO parent)
            {
                //((ScheduleVO)child).FlightList
                this.SaveChildrens(((ScheduleVO)child).FlightList, parent);
            }

     

            public override void SaveChildrens(List<BaseVO> childListToPersist, BaseVO parentVO)
            {
                foreach (BaseVO  child in childListToPersist)
                {
                    ORlayerEngine.Persist(child, parentVO);
                }
            }
        }

    ------------------------------------------------------------------Code using Generic at Method Level ------------------------------------------------------------------------------------------

    Case 2
    // generic type specified at method level , with the same constraint
      public abstract class BaseVOMapper
        {
            public abstract void Insert(BaseVO child , BaseVO parent );

            public abstract void SaveChildrens<T>(List<T> childListToPersist, BaseVO parentVO) where T:BaseVO ;
        }

    Below is the implementation of the derived class :

    public class ScheduleVOMapper: BaseVOMapper
        {
            public override void Insert(ValueObjects.BaseVO child, ValueObjects.BaseVO parent)
            {
                //((ScheduleVO)child).FlightList
                this.SaveChildrens(((ScheduleVO)child).FlightList, parent);
            }

            public override void SaveChildrens<T>(List<T> childListToPersist, BaseVO parentVO)
            {
                foreach (BaseVO child in childListToPersist)
                {
                    ORlayerEngine.Persist(child, parentVO);
                }
            }
        }

    Comparing Case 1 & Case 2 , in case 1 , in the derived class ScheduleVOMapper , the SaveChildrens method has the generic type explicity specified as BaseVO ,
    whereas in Case 2 ,  the method gets the Generic Type at runtime , which anyways  as per the constraint has to be of Type BaseVO.

    Though , I have my solution implemneted using Case 2 , would really like to get my doubt's cleared

     

    Many Thanks
    Girija

     

All Replies

  • Thursday, May 01, 2008 10:38 AM
    Moderator
     
     Answered
    List<Base> and List<Derived> are distinct types, you can't convert List<Derived> to List<Base>.  If it were possible, you could pass a List<Derived> to a method accepting List<Base> and that method could add an object of type Base to the list.  Which violates the promise that the original list only contains objects of type Derived.
  • Thursday, May 01, 2008 6:29 PM
     
     
    Thanks Sir ,for the simplistic explanation.

     

  • Tuesday, August 12, 2008 12:10 AM
     
      Has Code
    Great info and great response.  I have a similar situation that ties into this topic.  Hopefully you can help set me in the right direction.

    My original intent was to create a list of a particular type and create additional Add methods that convert to that type.   That seems to work fine and seems to follow the rules of a list.

        public class MyObjectList : System.Collections.Generic.List<MyObject>
        {  
              
            /// <summary> 
            ///     Creates a MyObject item from a MyOtherObject item and  
            ///       adds it to the list  
            /// </summary> 
            /// <param name="item">item is converted to MyObject then added to the list</param> 
            public void Add(MyOtherObject item)  
            {  
                MyObject MyObjectItem = new MyObject(item);  
                //Create a null item in the list (just like base.Add does)  
                if (null == MyObjectItem)  
                {  
                    //ALERT - Does this create a null item is it necessary???  
                    base.Add(null);  
                }  
                else  
                {  
                    base.Add(MyObjectItem);  
                }  
            }  
        } 

    Now, I want to hook up an "OnItemAdded" event to the class that fires when an item is added.  So  I added the event and hooked it up but it only fires on the non-standard Add.  I then tried to "override" Add (which doesn't work) so I tried to "hide" the base Add with a new like below but it doesn't fire under certain circumstances...
     
            /// <summary> 
            /// Adds the OnLineItemAdded event to the base Add method  
            /// </summary> 
            /// "new" hides the base version of Add.  
            new public void Add(DPILineItemInDetail item)  
            {  
                //Create a null item in the list (just like base.Add does)  
                if (null == item)  
                {  
                    //ALERT - Does this create a null item???  
                    base.Add(null);  
                }  
                else  
                {  
                    base.Add(item);  
                }  
                //Fire the OnLineItemAdded Event once a line itme has been added  
                DPILineItemListEventArgs e = new DPILineItemListEventArgs(this.IndexOf(item));  
                OnLineItemAdded(e);  
            } 

    The circumstance I've found that it doesn't fire under is when I add an item through the BindingSource / BindingNavigator I have it hooked to.
  • Tuesday, August 12, 2008 1:01 AM
    Moderator
     
     
    That's the problem with the "new" keyword.  Consider using the .NET 3.0 System.Collections.ObjectModel.ObservableCollection<T>, it fires events when the collection changes.
    Hans Passant.
  • Thursday, May 26, 2011 5:51 AM
     
     

    I'm pleased, as the answer to the query that I had raised 2-3 years back has finally been provided in the form of Covariance and contravariance in .Net 4.0 :).

    Have blogged about it at

    http://www.codeproject.com/Articles/253417/Understanding-Covariance-and-Contravariance-in-Net

    Thanks Girija