7

I have the following code:

public interface IMenuItem
{
    IWebElement Parent { get; }
}


public class MenuItems
{
    public T GetMenuItem<T>() where T : IMenuItem, new()
    {
        var menuItem = new T();

        return menuItem;
    }
}


public abstract class Page : IEnumerable<IComponent>
{
    private Func<IMenuItem> _getMenuItems = new MenuItems().GetMenuItem<IMenuItem>;
}

I'm trying to store the new MenuItems().GetMenuItem<IMenuItem> function in _getMenuItems field, but since this is a generic function this isn't working. How can I store generic functions into a variable?

Doing new MenuItems().GetMenuItem<IMenuItem> won't work and it tells me:

'IMenuItem' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method 'MenuItems.GetMenuItem()'

Now I have many concrete types that implement IMenuItem I want the delegate to accept those. I don't want to make a seperate delegate for every concrete type that implements IMenuItem. Rather I have a single delegate that can accept concrete types that implement IMenuItem.

O S
  • 453
  • 3
  • 14
  • 2
    Is your intent actually to create (and discard) a new `MenuItems` instance every time the delegate is invoked? That seems like a potential problem with the design. Either `GetMenuItem` should be static or the `MenuItems` instance should itself be created elsewhere. – Jeroen Mostert Dec 10 '19 at 11:44
  • @JeroenMostert Thanks for the comment! Would it be ok if I store the `new MenuItems()` item in a seperate field in the `Page` class? and in the delegate i do `GetMenuItem`? – O S Dec 10 '19 at 11:48
  • 2
    That would work -- except, as others have noted, you do need to pass a concrete type to `GetMenuItem`, `IMenuItem` on its own won't do it. You may wish to instead leave it up to `MenuItems` to determine the type (by making it a `MenuItems`, for example), or else you want to pass a `Func` to *it* rather than have a `new()` constraint, so it's up to callers to decide what to instantiate and how. – Jeroen Mostert Dec 10 '19 at 11:53
  • 2
    actually this is a good question, so did I understand you correctly that your would like to use this variable `_getMenuItems` like this: `IMenuItem temp = _getMenuItems()` ? and you want this as a variable and not a method ?=! because a method would only be a wrapper for the `new MenuItems().GetMenuItem()` call ?=! – Mong Zhu Dec 10 '19 at 12:17
  • You should ask yourself, what do you want to solve? You are asking solution for something we don't know what you try to solve (XY Problem, wanting to do X, but ask for Y cause you think Y is the right way). If its to populate a drop down menu in the view, why not use regular classes and POCOs, populate them and return them rather than trying to replicate desktop application behavior/patterns to a web application. Web applications are not desktop applications and are fundamentally different in the way they are presented – Tseng Dec 10 '19 at 12:25
  • 1
    @MongZhu that's correct! I don't need a method wrapper I want to do this in a variable like you said: `IMenuItem temp = _getMenuItems()` – O S Dec 10 '19 at 12:29
  • 1
    I think you meant to write `private Func _getMenuItems = () => menuItems.GetMenuItem();` – John Alexiou Dec 10 '19 at 13:15
  • @OS Do you consider your question as solved ? Usually either you use GetMenuItems with a real constructible type, or you do not need the generic at all, and just return always an IMenuItem. – Holger Dec 10 '19 at 13:19
  • @ja72 yes that's correct, I edited the post 30 mins ago, it should be clear now I think. – O S Dec 10 '19 at 13:24
  • @Holger the idea is to define that delegate so that my child classes that inherit `Page` can use that delegate and pass real constructible types. – O S Dec 10 '19 at 13:34
  • I don't see the corrected question. Are you sure you [edit] the question? – John Alexiou Dec 10 '19 at 13:54
  • @OS. Mixing delegates with inheritance most often makes no sense. Use an abstract functions and override it - if you decided to use inherit from page. A delegate is the very same thing as an abstract class with only one abstract method. It's sometimes more convenient than building a class hierarchy with overrides, but if you do this anyway, the delegate makes no sense any more. Especially you define here, all the subclass should use the same delegate instance, that makes no sense a second time. You struggle here on setting the Default Value of this delegate-field, on nothing else. – Holger Dec 10 '19 at 13:56

3 Answers3

6

The problem is with the new() constraint.

The new constraint specifies that a type argument in a generic class declaration must have a public parameterless constructor. To use the new constraint, the type cannot be abstract.

Since IMenuItem is an interface, it cannot be instantiated, and therefore the new() constraint will be a problem.

Edit:

You can solve this in a couple of ways:

  1. You can use reflection for retrieving the method and invoking it:

    var method = typeof(MenuItems).GetMethod("GetMenuItem");
    var instance = new MenuItems(); // Maybe convert the class into a static class??
    var genericMethod = method.MakeGenericMethod(typeof(MenuItem1));
    var result = genericMethod.Invoke(instance, null) as IMenuItem;
    
  2. Use dynamic to invoke it:

    dynamic instance = new MenuItems(); // Maybe convert the class into a static class??
    var result = instance.GetMenuItem<MenuItem1>() as IMenuItem;
    
  3. Hold an instance of MenuItems instead of the method itself:

    var menuItems = new MenuItems();
    var result = _instance.GetMenuItem<MenuItem1>() as IMenuItem;
    
Amir Popovich
  • 29,350
  • 9
  • 53
  • 99
0

I don't think a field is appropriate here. The best you can do is a wrapper method:

public abstract class Page : IEnumerable<IComponent>
{
    private static readonly MenuItems menuItems = new MenuItems();        

    protected Func<T> GetMenuItems<T>() where T : IMenuItem, new()
    {
        return () => menuItems.GetMenuItem<T>();
    }

}
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
0

In your MenuItems class, new means

That is a constraint on the generic parameter T. It must be a class (reference type) and must have a public parameter-less default constructor.

That means T can't be an int, float, double, DateTime or any other struct (value type).

Source: https://stackoverflow.com/a/4737985/1349365

So what you can do with your current structure is :

public interface IMenuItem
{
    IWebElement Parent { get; }
}

public class MenuItem : IMenuItem
{
    public IWebElement Parent => throw new NotImplementedException();
}

public class MenuItems
{
    public T GetMenuItem<T>() where T : IMenuItem, new()
    {
        var menuItem = new T();

        return menuItem;
    }
}
public abstract class Page : IEnumerable<IComponent>
{
    private Func<IMenuItem> _getMenuItems = new MenuItems().GetMenuItem<MenuItem>;
}

As your error says, you need a concrete class to pass into GetMenuItem method.

Community
  • 1
  • 1
Moshi
  • 1,385
  • 2
  • 17
  • 36
  • I have many concrete classes that implement `IMenuItem` with the solution you gave I would need to create a delegate for every concrete class. – O S Dec 10 '19 at 13:36
  • Then as @amir-popovich suggests `var menuItems = new MenuItems(); var result = _instance.GetMenuItem() as IMenuItem;` is best choice. – Moshi Dec 10 '19 at 13:52