1

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.

3
  • 1
    Question: have you gotten out a profiler and measured the performance? Is it even a problem? Are you sure this is the cause of your problem?
    – Becuzz
    Feb 28, 2017 at 21:05
  • Well casting an object is a problem that gives performance overhead. Why do I need to use it when I can avoid it? It isn't causing a problem now, it would cause it latter and I would have to spend more time later changing the code that depends on it also if this won't work as expected. Why would I waste my time for that, when I can solve problem now. Mar 1, 2017 at 10:18
  • @Becuzz probably you are right codeproject.com/Articles/8052/… with as I can check very very fast and it won't matter, but now It's just a curiosity if this can actually be done. I always search for the best ways. Mar 1, 2017 at 10:56

5 Answers 5

1

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);
    }
}
2
  • Wow, great! I will definitely try that. One limitation I found with this approach of mine initially - that it only works for 1 instance of UI type, which is ok for the most part. I have been using similar one, though, without a controller. Now that I know there is a way to make it castless it's great, I will definitely use it. After a lot of research I found that it's not that performance heavy to cast an object if it's only a few objects. But it's good to know the capabilities of language. And this code would look much cleaner :) Dec 28, 2018 at 16:29
  • I will attach this link here for anyone that checks out this answer: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/… Dec 28, 2018 at 16:41
2

I think you need some means of binding a component to the type of data it accepts. Otherwise, how do you know they match up (other than by parsing the name and using your human intuition, which a computer can't do)?

This can be done with generics and inheritance, like this:

abstract class MyComponentData
{
    abstract public string ElementComponentType { get; }
}
class MyComponentData1 : MyComponentData
{
    override public string ElementComponentType { get { return "Type1"; } }
}
class MyComponentData2 : MyComponentData
{
    override public string ElementComponentType { get { return "Type2"; } }
}

abstract class MyComponentClass
{
}
class MyComponentClass<T> : MyComponentClass where T: MyComponentData
{
    public void AcceptData(T input)
    {
        //Do something with the data
    }
}

With this scheme, it is required to declare what type of data a particular component will accept as part of the declaration of the generic class MyComponentClass. So if you have a component that can handle MyComponentData1, you have to declare it as var c = new MyComponentClass<MyComponentData1>(). Thus any instance of a component is bound to a specific data type.

To initialize your lookup list:

Dictionary<string, MyComponentClass> _userInterfaceElements = new Dictionary<string, MyComponentClass>();
_userInterfaceElements.Add("Type1", new MyComponentClass<MyComponentData1>());
_userInterfaceElements.Add("Type2", new MyComponentClass<MyComponentData2>());

And to dispatch an incoming request for a component, passing the data, using the table:

static void TransferUIDataToComponent<T>(T data) where T: MyComponentData
{
    MyComponentClass<T> c = _userInterfaceElements[data.ElementComponentType] as MyComponentClass<T>;
    c.AcceptData(data);
}

But... hey.... we don't actually need the table any more! So you can just do this:

static void TransferUIDataToComponent<T>(T data) where T: MyComponentData
{
    var c = new MyComponentClass<T>();
    c.AcceptData(data);
}

Meaning you can get rid of _userInterfaceElements, get rid of ElementComponentType, and get rid of the abstract MyComponentClass that I added (we only needed it for _userInterfaceElements so we could store any component). And notice we have completely removed the need for any cast.

Then, to call the dispatch:

var data1 = new MyComponentData1();
TransferUIDataToComponent<MyComponentData1>(data1);

var data2 = new MyComponentData2();
TransferUIDataToComponent<MyComponentData2>(data2);
2
  • Thank you for your answer, but it really makes thing even worse. I'm developing a game and overhead of creating 10000 objects per second is even worse than casting them, also GC would freak out. I have this kind of system where you can get corresponding type - as I wrote (you can see code above) that I have enum that gets type I need - it's not a problem to get it. All of my data classes and elements are created before game start, so I can just update them and I don't need creating new ones if I don't have new Instances of game objects. What I need is to both avoid casting and object creation. Feb 28, 2017 at 8:35
  • If it's really impossible, I said I can change the whole thing and just follow the best practice here can be - it's another question. But now I only need to know if it's possible. Feb 28, 2017 at 8:37
1

What @JohnWu outlines is 'regular' polymorphism: ie implement a common abstraction across all implementation types to avoid casting. If you can do that, do that.

But depending on your scenario, sometimes this doesn't work. What you seem to be asking for in your question is double-dispatch: you want the type of both the class implementing the interface and the underlying type of the parameter to control dispatch binding (so you end up in a method with a strongly typed parameter).

If this is what you are after, you can't have it. But you can cheat: the Vistor pattern enables you to implement these kind of arrangements using 'regular' polymorphic dispatch, based on the fact that you end up doing a single dispatch twice. You pass the target to the parameter's interface, and the parameter passes himself, strongly-typed, to the target.

Eric Lippert wrote a really good blog post about this which makes much more sense than the explanation above: https://ericlippert.com/2015/05/04/wizards-and-warriors-part-three/

1
  • So far it's the best answer, but I will have to wait, maybe someone has an idea. The problem with Visitor - I would have to create minimum 2 methods per new element in Controller and Manager that violate SOLID. They should not depend on any data. I have an idea of having any data return an appropriate array of values and parse them the way I need in this class, though I want to avoid creating new arrays, so I will probably pass an internal array which is unsafe because data can be modified then. But It's good for performance and I hope no one will actually try to change data there. Feb 28, 2017 at 9:08
1

Wow dude, you hated my other solution. Guess I didn't understand your NFRs properly.

Here's another solution that keeps the list (so no micro-instantiations required) and still has no casts. Hope you're happy.

The idea here is that we remove from the base class any method prototypes that need to be type-specific; after all, the base class has no idea what its derived type is. Instead we implement a common Apply method which accepts the controller as an argument. This is a type of dependency injection, but at the method level. The controller is serving double duty as a sort of factory, which can return existing, strongly-typed components. Each data class knows how to find its partner component using generics.

Once the component is obtained, it's a simple matter of a call to the AcceptUserInterfaceElementComponentData method which is now type-specific and therefore can have strongly-typed arguments.

I did get rid of the enum... using an enum to identify type seemed like a code smell to me.

abstract public class UserInterfaceElementComponentData
{
    abstract public void Apply(UserInterfaceController controller);
}

public class UserInterfaceElementComponentDataRightCorner : UserInterfaceElementComponentData
{
    public string TypeSpecificExample
    {
        get
        {
            return "I am a UserInterfaceElementComponentDataRightCorner!!! I'm even hardcoded!!!";
        }
    }

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


abstract public class UserInterfaceElementComponent
{
}


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

public class UserInterfaceController
{
    List<UserInterfaceElementComponent> _userInterfaceElements = new List<UserInterfaceElementComponent>();

    public UserInterfaceController()
    {
        _userInterfaceElements.Add( new UserInterfaceElementRightCorner());
    }
    public void TransferUIDataToComponent<T>(T userInterfaceElementComponentData) where T : UserInterfaceElementComponentData
    {
        userInterfaceElementComponentData.Apply(this);
    }

    public T ResolveComponent<T>()
    {
        var result =  _userInterfaceElements.OfType<T>().FirstOrDefault();
        if (result == null) throw new InvalidOperationException(string.Format("Component {0} not found", typeof(T).FullName));
        return result;
    }
}
static class CastlessDispatch
{
    static UserInterfaceController _userInterfaceController = new UserInterfaceController();
    public static void TransferDataToUIController<T>(T userInterfaceElementComponentData) where T : UserInterfaceElementComponentData
    {
        _userInterfaceController.TransferUIDataToComponent(userInterfaceElementComponentData);
    }
}
10
  • Though I have one more question - var result = _userInterfaceElements.OfType<T>().FirstOrDefault(); - is it not casting? - It can even be worse, I would go through an array of elements and check their types - O(n), then get the right type and cast it to expected type, so actually it changes nothing. Mar 1, 2017 at 10:06
  • Also, OfType<T>() is creating an array of IEnumerable when returning a value, so it creates and object inside, this is worse than anything, sorry, but I have to uncheck this answer. If you don't belive me look here: stackoverflow.com/a/7195497/6603717 Mar 1, 2017 at 10:26
  • I know what it does, I am just adjusting to your requirements, which so far are: (1) no casting, (2) no instantiating components, (3) have to store component instances in a list, and now (4) can't enumerate on the list. Got it. Anything else?
    – John Wu
    Mar 1, 2017 at 10:48
  • And (5) all class and method names must be unreasonably long ;)
    – John Wu
    Mar 1, 2017 at 10:50
  • But there is casting, if you don't see it, it doesn't mean that it doesn't exist. Also, making code like a spaghetti compared to what I have now and making performance even worse with creating array and exception check - isn't going to solve my problem. Mar 1, 2017 at 10:51
1

One of the limitations of the c# language is that there is only one type identifier for an array or list, so every element must be the same type, or derive from the same type. If it is derived, the only way to get the subtype out of the array is to cast it. This is true no matter how clever you are with generics.

Here is a solution that may seem a little ugly but it solves the casting problem by getting away from the generics and initializing type-safe pointers that can be used without any casting.

I kept the list, to keep you happy, but it doesn't do anything in my example.

abstract public class UserInterfaceElementComponentData
{
    abstract public void Apply(UserInterfaceController controller);
}

public class UserInterfaceElementComponentDataRightCorner : UserInterfaceElementComponentData
{
    public string TypeSpecificExample
    {
        get
        {
            return "I am a UserInterfaceElementComponentDataRightCorner!!! I'm even hardcoded!!!";
        }
    }

    public override void Apply(UserInterfaceController controller)
    {
        var component = controller.UserInterfaceElementRightCorner;
        component.AcceptUserInterfaceElementComponentData(this);
    }
}


abstract public class UserInterfaceElementComponent
{
}


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

public class UserInterfaceController
{
    List<UserInterfaceElementComponent> _userInterfaceElements = new List<UserInterfaceElementComponent>();
    private UserInterfaceElementRightCorner _userInterfaceElementRightCorner;

    public UserInterfaceController()
    {
        _userInterfaceElementRightCorner = new UserInterfaceElementRightCorner());
        _userInterfaceElements.Add( _userInterfaceElementRightCorner );
    }
    public UserInterfaceElementRightCorner UserInterfaceElementRightCorner
    {
        get { return _userInterfaceElementRightCorner; }
    }
    public void TransferUIDataToComponent(UserInterfaceElementComponentData userInterfaceElementComponentData)
    {
        userInterfaceElementComponentData.Apply(this);
    }
}
static class CastlessDispatch
{
    static UserInterfaceController _userInterfaceController = new UserInterfaceController();
    public static void TransferDataToUIController(UserInterfaceElementComponentData userInterfaceElementComponentData)
    {
        _userInterfaceController.TransferUIDataToComponent(userInterfaceElementComponentData);
    }
}
1
  • I got the thing that it's impossible to pass any type as generic or base class and then get it without casting. Thanks for your eagerness to help. Your answers a legit, but I asked for impossible. Mar 1, 2017 at 12:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.