0

I have a web page where a user can drag and drop certain widgets into a single placeholder. Each widget has its own set of properties.

For example, suppose I have the following HTML widgets:

Widget1:
 - Title field
 - Technical name field

And another widget that is more dynamic:

Widget2:
 - Range picker field 1 (0 to 100)
 - Range picker field 2 (0 to 100)
               ------------------
               | (+) Add picker |
               ------------------
 (can add as many range pickers as needed)

Now with the click of a button these settings are saved to the database. But at some point I also need to read that data from it again.

For example, suppose I have the following classes for the Widgets:

interface IWidget { }

class Widget1 : IWidget
{
    public string Title {get;set;}
    public string TechnicalName {get;set;}
}

class Widget2 : IWidget
{
    public List<int> PickerValues {get;set;}
}

Then store that in a List.

var widgets = new List<IWidget>();
widgets.Add(new Widget1());
widgets.Add(new Widget2());

That would work. But then getting an object from that list again is a problem:

var widget = widgets.First();
widget.?????   // <-- NO INTELLISENSE!

That means it doesn't know what actual implementation it has. I'd have to cast it. But how, if I don't know what widgets are stored in there? It can be a list of 1, 5 or even 10 different widgets in any order.

How can I solve this problem?

Vivendi
  • 20,047
  • 25
  • 121
  • 196
  • Just as a side note: http://stackoverflow.com/questions/7552677/are-empty-interfaces-code-smell – Habib May 04 '15 at 19:49
  • 2
    The point of creating an abstraction with an interface is that *you shouldn't care* about the concrete implementation. Currently, your interface is meaningless. What do you want to do with those widgets? – Yuval Itzchakov May 04 '15 at 19:50
  • What do you want to do with the IWidget once you pull it out of the list? – Moby Disk May 04 '15 at 19:50
  • you can see about [as](https://msdn.microsoft.com/en-us//library/cscsdfbt.aspx) and [is](https://msdn.microsoft.com/en-us//library/scekt9xw.aspx) keywords – Grundy May 04 '15 at 19:51
  • `IWidget` doesn't define any members, so you won't get any intellisense. You need to place common functionality in the interface, otherwise yes, you will have to cast. – Ron Beyer May 04 '15 at 19:51
  • Why you need to cast? what operation do you expect to do after retrieve the widget? If its a common behaviour to all widgets, it should be a method of the interface and each widget implements its specific behaviour – Rodrigo López May 04 '15 at 19:54
  • With an empty interface, you may as well have a `List`, because you will have to check the type of each item and then cast in order to get intellisense... – Rufus L May 04 '15 at 19:58

3 Answers3

1

You currently have a list of objects that all implement the IWidget interface. Accessing them directly will only give you intellisense on the properties of IWidget (which has none).

In order to resolve this, you either need to put some properties in the IWidget interface, or check the type of each item and then cast it, like:

IWidget widget = widgets.First();

var widget1 = widget as Widget1;

if (widget1 != null)
{
    widget1.TechnicalName = "new name"; // <-- We have intellisense
}

You could also have some logic that checks each widget in a loop, like:

foreach (var widget in widgets)
{
    if (widget is Widget1)
    {
        var widget1 = (Widget1) widget;
        // Do something with Widget1 types here
    }
    else if (widget is Widget2)
    {
        var widget2 = (Widget2)widget;
        // Do something with Widget2 types here
    }
}

Alternatively, you could just get the first Widget1 (or Widget2) from the list by doing something like:

Widget1 widget = widgets.OfType<Widget1>().First();
Rufus L
  • 36,127
  • 5
  • 30
  • 43
1

You will need to use Polymorphism to address your issue:

interface IWidget 
{
    public void readData();
}

class Widget1 : IWidget
{
    public string Title { get; set; }
    public string TechnicalName { get; set; }

    public void readData()
    {
        Console.WriteLine("- Title: {0}\n- Technical name: {1}", Title, TechnicalName);
    }
}

class Widget2 : IWidget
{
    public List<int> PickerValues { get; set; }

    public void readData()
    {
        StringBuilder builder = new StringBuilder();
        for (int i = 1; i <= PickerValues.Count; i++)
        {
            builder.AppendLine(String.Format("- Range picker field {0}: {1}",i,PickerValues[i-1]));
        }
        Console.WriteLine(builder.ToString());
    }
}

And Later on...

        var widgets = new List<IWidget>();
        widgets.Add(new Widget1());
        widgets.Add(new Widget2());
        var widget = widgets.First();
        widget.readData(); // will print correct output for each type of widget, as implemented on each widget.
Rodrigo López
  • 4,039
  • 1
  • 19
  • 26
0

Maybe your choice is to type-check the instance first:

var widget = widgets.First();
if (widget is Widget1)
{
    var widget1 = (Widget1) widget;
    widget1.DoSomethingSpecificToWidget1();
}

Alternatively, with additional LinQ functionality, you can easily split your list to genric sub-lists. This will work well if you do not need to process the widgets in the order they are stored in the widgets variable.

var w1List = widgets.OfType<Widget1>();// you may also call `.ToList()`
var w2List = widgets.OfType<Widget2>();

foreach (var w1 in w1List)
{
    w1.DoSomethingSpecificToWidget1(); // w1 is now Widget1
}
Ivaylo Slavov
  • 8,839
  • 12
  • 65
  • 108