How to Avoid Object Cast After Passing a Parameter

Architecturegenericsinheritanceobject-orientedpolymorphism

I have a problem with implementing generic user interface interaction.

I have different classes that contain data each for particular interface element. So every UserInterfaceElementComponent has data only useful for him – all data inherit from UserInterfaceElementComponentData. For example,
UserInterfaceElementRightCorner only can use data of type UserInterfaceElementComponentDataRightCorner.

The problem is in generic part – I have class UserInterfaceManager that has a generic method:

public static void TransferDataToUIController<T>(T userInterfaceElementComponentData) where T: UserInterfaceElementComponentData
{
    _userInterfaceController.TransferUIDataToComponent(userInterfaceElementComponentData);
}

So from any class I can send it any data that inherits from UserInterfaceElementComponentData. And it asks controller to transfer this data to particular UserInterfaceElementComponent by geting its enum type:

public void TransferUIDataToComponent<T>(T userInterfaceElementComponentData) where T: UserInterfaceElementComponentData
{
    this._userInterfaceElements[(int)userInterfaceElementComponentData.ElementComponentType].AcceptUserInterfaceElementComponentData(userInterfaceElementComponentData);
}

So it gets particular UI component from this array this._userInterfaceElements that corresponds to this data type. Then it calls to accept data that was given to him.

public class UserInterfaceElementRightCorner : UserInterfaceElementComponent {

    public override void AcceptUserInterfaceElementComponentData(UserInterfaceElementComponentData userInterfaceElementComponentData)
    {
        ((UserInterfaceElementComponentDataRightCorner)userInterfaceElementComponentData)._planetInfoPanel.gameObject.SetActive(true);
    }
}

Though need to cast this object to corresponding type to get this particular data, so I could use it in that class in way I need, but I would like to avoid object cast because it will be called more than 10000 times per second.

I don't mind changin the whole architecture, I just need to pass object as parameter and it should already be the type I need. Though with generics it seems impossible.

Otherwise, I will have to create a new method for every type in 2 classes and compare types. In general I need to pass particular data type to particular client without casting and with 1-2 methods.

Best Answer

Fourth time is the charm

This is the same as my second answer (the one you initially checked) and I got rid of the <OfType> call and the enumerator.

The trick here was to define a contravariant interface that pairs components and their data. I have been meaning to learn more about contravariance so this was a fun exercise, thank you.

This may be a bit of messy code at this point, but I think I've proven it can be done, and you can use the same technique but maybe clean up the structure a bit.

public enum ElementComponentTypeEnum { RightCorner = 1 }

public interface IRelated<in T1, in T2>  
{ 
    void AcceptUserInterfaceElementComponentData(T2 t2);
}
abstract public class UserInterfaceElementComponentData
{
    abstract public ElementComponentTypeEnum ElementComponentType { get; }
    abstract public void Apply(UserInterfaceController controller);
}

public class UserInterfaceElementComponentDataRightCorner : UserInterfaceElementComponentData
{
    public string TypeSpecificString
    {
        get
        {
            return "I am a UserInterfaceElementComponentDataRightCorner!!! I'm even hardcoded!!!";
        }
    }
    override public ElementComponentTypeEnum ElementComponentType { get { return ElementComponentTypeEnum.RightCorner; } }

    public override void Apply(UserInterfaceController controller)
    {
        IRelated<UserInterfaceElementRightCorner, UserInterfaceElementComponentDataRightCorner> component = controller.ResolveComponent<UserInterfaceElementRightCorner,UserInterfaceElementComponentDataRightCorner>(this.ElementComponentType);
        component.AcceptUserInterfaceElementComponentData(this);
    }
}


abstract public class UserInterfaceElementComponent : IRelated<UserInterfaceElementComponent, UserInterfaceElementComponentData>
{
    public void AcceptUserInterfaceElementComponentData(UserInterfaceElementComponentData data) { }
}


public class UserInterfaceElementRightCorner : UserInterfaceElementComponent, IRelated<UserInterfaceElementRightCorner,UserInterfaceElementComponentDataRightCorner>
{
    public void AcceptUserInterfaceElementComponentData(UserInterfaceElementComponentDataRightCorner data) 
    {
        Console.WriteLine("Hey I'm doing sonmething with a specific, uncast data class.  Here's proof: " + data.TypeSpecificString);
    }

}

public class UserInterfaceController
{
    private UserInterfaceElementComponent[] _userInterfaceElements = new UserInterfaceElementComponent[10];

    public UserInterfaceController()
    {
        _userInterfaceElements[(int)ElementComponentTypeEnum.RightCorner] = new UserInterfaceElementRightCorner();
    }
    public void TransferUIDataToComponent<T>(T userInterfaceElementComponentData) where T : UserInterfaceElementComponentData
    {
        userInterfaceElementComponentData.Apply(this);
    }

    public IRelated<T1,T2> ResolveComponent<T1,T2>(ElementComponentTypeEnum elementComponentType) where T1 : UserInterfaceElementComponent where T2: UserInterfaceElementComponentData
    {
        IRelated<T1,T2> result = _userInterfaceElements[(int)elementComponentType];
        return result;
    }
}
static public class CastlessDispatch
{
    static UserInterfaceController _userInterfaceController = new UserInterfaceController();
    public static void TransferDataToUIController<T>(T userInterfaceElementComponentData) where T : UserInterfaceElementComponentData
    {
        _userInterfaceController.TransferUIDataToComponent(userInterfaceElementComponentData);
    }
}