locked
Can one define a member variable for a generic delegate ? RRS feed

  • Question

  • I have the Car class as described here under.

    I was trying to see if I could define a private member variable of the delegate type as follows :

    // Define a member variable of this delegate :
    private CarEngineHandler<string> listOfHandlers;

    but it doesn't work. Can anyone please advise where the code is incorrect or is it just not possible to declare a member variable in this manner when using generics ?

    Any assistance would be gratefully appreciated.

    Thank you.

    public class Car
        {
            // Define a delegate type :
            public delegate void CarEngineHandler<T>(T msgForCaller);

            // Define a member variable of this delegate :
           private CarEngineHandler<T> listOfHandlers;

            // Add a registration function for the caller (this helper function is required ,
            // because we made our member variable , listOfHandlers , private, thereby making
            // it inaccessible outside this class) :
            public void RegisterWithCarEngine(CarEngineHandler<T> methodToCall)
            {
                //listOfHandlers = methodToCall;
                // TO make the delegate "multicast"
                listOfHandlers += methodToCall;
            }

            // Add a method which allows a caller to deregister a method from the delegate's
            // invocation list :
            public void DeregisterWithCarEngine(CarEngineHandler<T> methodToCall)
            {
                listOfHandlers -= methodToCall;
            }

            // Internal state data :
            public int CurrentSpeed { get; set; }
            public int MaxSpeed { get; set; } = 100;
            public string PetName { get; set; }

            // Is the car dead or alive ?
            private bool carIsDead;

            // Class Constructors :
            public Car() { }

            public Car(string name, int maxSp, int currSp)
            {
                CurrentSpeed = currSp;
                MaxSpeed = maxSp;
                PetName = name;
            }

            // At this point, you need to create the Accelerate() method.
            // Recall, the point here is to allow a Car object
            // to send engine-related messages to any subscribed listener.
            public void Accelerate(int delta)
            {
                // If the car is dead, send message :
                if (carIsDead)
                {
                    if (listOfHandlers != null)
                    {
                        listOfHandlers("Sorry, this car is dead !");
                    }
                }
                else
                {
                    CurrentSpeed += delta;

                    // Is this car "almost dead"
                    if (10 == (MaxSpeed - CurrentSpeed) && listOfHandlers != null)
                    {
                        listOfHandlers("Careful ! The car engine is going to seize.");
                    }
                    if (CurrentSpeed >= MaxSpeed)
                    {
                        carIsDead = true;
                    }

                    else
                        Console.WriteLine("Current speed = {0} : ", CurrentSpeed);
                }

            }
        }
    }

                        
    Monday, August 3, 2020 11:14 AM

Answers

  • Hi, The <T> in the delegate definition is for the pattern.  You have to use a type such as string etc to call this delegate.

    public delegate void CarEngineHandler<T>(T msgForCaller);

    private CarEngineHandler<string> listOfHandlers;



    Monday, August 3, 2020 11:55 AM
  • I'm not sure I follow why you need a generic handler here. Let's just focus on your use of a generic and talk this through.

    public class Car
    {
       // Define a delegate type :
       public delegate void CarEngineHandler<T>(T msgForCaller);
    
       // Define a member variable of this delegate :
       private CarEngineHandler<T> listOfHandlers;
    }

    This declaration is saying you want a delegate to handle T but what is T? Generics are for generalizing algorithms, not for allowing arbitrary types at runtime. If you want arbitrary types at runtime you'd just use `object`. At the point of compilation you need to know what T is. Given Car how would one instance specify they want T to be string and another int, for example? The only way to do that is to put the generic type into the type declaration of Car.

    public class Car<T>
    {
        // Define a delegate type :
        public delegate void CarEngineHandler<T>(T msgForCaller);
    
        // Define a member variable of this delegate :
        private CarEngineHandler<T> listOfHandlers;
    }

    Now you can declare an instance of Car<string> and the delegate requires a string. You can declare an instance of Car<int> and that instance requires an int for the delegate. In general, in C#, if you use a generic in a property, field or event then you must make the entire type generic. If you want to make a method generic then you can do so on a case by case basis or if all the methods in a type use the same generic type parameter then the entire type can be generic.

    But ultimately it probably doesn't make sense to use a generic for the delegate here. The problem is that you'd use a delegate in order to be able to call back to some code later. But the delegate generic type wouldn't be tied to anything the type could know about so how could you possibly call it?

    void CallWithString ( string msgForCaller )
    {
       //?
    }
    
    void CallWithInt ( int msgForCaller )
    {
       //?
    }
    
    var car = new Car();
    car.RegisterWithCarEngine(CallWithString);
    car.RegisterWithCarEngine(10);
    
    //Later in code
    // listOfHandlers("Hello"); //Works for CallWithString but not CallWithInt
    
    

    Since Car calls the delegate with a message the delegate should require a string, no generics needed. If it needed something else (say CarData) then you'd change the delegate to accept that instead.

    I should also note that you don't really need a delegate here. A raw delegate is generally used for calling a method but you're using it more like an event so I'd recommend you just use an event instead. It requires less code and is more natural to C# code.


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by Whomsoever Tuesday, August 4, 2020 3:37 PM
    Monday, August 3, 2020 2:20 PM
  • Hi Whomsoever,

    Thank you for posting here.

    I agree with Michael Taylor. According to the logic of the existing code, generics are not necessary, the type can only be string.

    In Accelerate(), this code will only be triggered when the method is called again after the car is dead, but the car should not accelerate again after death, it is illogical.

                if (carIsDead)
                {
                    if (listOfHandlers != null)
                    {
                        listOfHandlers("Sorry, this car is dead !");
                    }
                }

    In addition, must the speed of the car increase be a multiple of 10?

    If not, the following code may not trigger.

                    if (10 == (MaxSpeed - CurrentSpeed) && listOfHandlers != null)
                    {
                        listOfHandlers("Careful ! The car engine is going to seize.");
                    }

    I made some changes to this method, please see if it suits you.

                if (listOfHandlers == null)
                {
                    //do something
                }
                else
                {
                    CurrentSpeed += delta;
                    if (CurrentSpeed >= MaxSpeed)
                    {
                        listOfHandlers("Sorry, this car is dead !");
                        return;
                    }
                    if (10 >= (MaxSpeed - CurrentSpeed))
                    {
                        listOfHandlers("Careful ! The car engine is going to seize.");
                    }
                    Console.WriteLine("Current speed = {0}", CurrentSpeed);
                }

    In this code, carIsDead is not needed.

    Best Regards,

    Timon


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    • Marked as answer by Whomsoever Tuesday, August 4, 2020 3:36 PM
    Tuesday, August 4, 2020 7:03 AM

All replies

  • Hi, The <T> in the delegate definition is for the pattern.  You have to use a type such as string etc to call this delegate.

    public delegate void CarEngineHandler<T>(T msgForCaller);

    private CarEngineHandler<string> listOfHandlers;



    Monday, August 3, 2020 11:55 AM
  • I'm not sure I follow why you need a generic handler here. Let's just focus on your use of a generic and talk this through.

    public class Car
    {
       // Define a delegate type :
       public delegate void CarEngineHandler<T>(T msgForCaller);
    
       // Define a member variable of this delegate :
       private CarEngineHandler<T> listOfHandlers;
    }

    This declaration is saying you want a delegate to handle T but what is T? Generics are for generalizing algorithms, not for allowing arbitrary types at runtime. If you want arbitrary types at runtime you'd just use `object`. At the point of compilation you need to know what T is. Given Car how would one instance specify they want T to be string and another int, for example? The only way to do that is to put the generic type into the type declaration of Car.

    public class Car<T>
    {
        // Define a delegate type :
        public delegate void CarEngineHandler<T>(T msgForCaller);
    
        // Define a member variable of this delegate :
        private CarEngineHandler<T> listOfHandlers;
    }

    Now you can declare an instance of Car<string> and the delegate requires a string. You can declare an instance of Car<int> and that instance requires an int for the delegate. In general, in C#, if you use a generic in a property, field or event then you must make the entire type generic. If you want to make a method generic then you can do so on a case by case basis or if all the methods in a type use the same generic type parameter then the entire type can be generic.

    But ultimately it probably doesn't make sense to use a generic for the delegate here. The problem is that you'd use a delegate in order to be able to call back to some code later. But the delegate generic type wouldn't be tied to anything the type could know about so how could you possibly call it?

    void CallWithString ( string msgForCaller )
    {
       //?
    }
    
    void CallWithInt ( int msgForCaller )
    {
       //?
    }
    
    var car = new Car();
    car.RegisterWithCarEngine(CallWithString);
    car.RegisterWithCarEngine(10);
    
    //Later in code
    // listOfHandlers("Hello"); //Works for CallWithString but not CallWithInt
    
    

    Since Car calls the delegate with a message the delegate should require a string, no generics needed. If it needed something else (say CarData) then you'd change the delegate to accept that instead.

    I should also note that you don't really need a delegate here. A raw delegate is generally used for calling a method but you're using it more like an event so I'd recommend you just use an event instead. It requires less code and is more natural to C# code.


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by Whomsoever Tuesday, August 4, 2020 3:37 PM
    Monday, August 3, 2020 2:20 PM
  • Hi Whomsoever,

    Thank you for posting here.

    I agree with Michael Taylor. According to the logic of the existing code, generics are not necessary, the type can only be string.

    In Accelerate(), this code will only be triggered when the method is called again after the car is dead, but the car should not accelerate again after death, it is illogical.

                if (carIsDead)
                {
                    if (listOfHandlers != null)
                    {
                        listOfHandlers("Sorry, this car is dead !");
                    }
                }

    In addition, must the speed of the car increase be a multiple of 10?

    If not, the following code may not trigger.

                    if (10 == (MaxSpeed - CurrentSpeed) && listOfHandlers != null)
                    {
                        listOfHandlers("Careful ! The car engine is going to seize.");
                    }

    I made some changes to this method, please see if it suits you.

                if (listOfHandlers == null)
                {
                    //do something
                }
                else
                {
                    CurrentSpeed += delta;
                    if (CurrentSpeed >= MaxSpeed)
                    {
                        listOfHandlers("Sorry, this car is dead !");
                        return;
                    }
                    if (10 >= (MaxSpeed - CurrentSpeed))
                    {
                        listOfHandlers("Careful ! The car engine is going to seize.");
                    }
                    Console.WriteLine("Current speed = {0}", CurrentSpeed);
                }

    In this code, carIsDead is not needed.

    Best Regards,

    Timon


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    • Marked as answer by Whomsoever Tuesday, August 4, 2020 3:36 PM
    Tuesday, August 4, 2020 7:03 AM
  • My sincere thanks to all of you for your time and effort in replying .  Thank you
    Tuesday, August 4, 2020 3:37 PM
  • Hi

    Try to pass the type like bellow .The bellow code may help you.

     public static void MyRegisterWithCarEngine(int i)
            {
                Console.WriteLine(i + i);
    
            }
            static  CarEngineHandler<int> listOfHandler = new CarEngineHandler<int>(multinumber);
            public static void RegisterWithCarEngine(CarEngineHandler<int> methodToCall)
            {
                listOfHandler += methodToCall;
                int x = 5;
                CarEngineHandler<int> listOfHandlers = new CarEngineHandler<int>(multinumber);
                listOfHandlers(x);
                //listOfHandlers = methodToCall;
                // TO make the delegate "multicast"
                listOfHandlers += multinumber;
            }

    Thanks and regards

    Tuesday, August 4, 2020 3:51 PM