1

I have an interface that extends some other interface, like this:

interface IBase
{
  int Id { get; set; }
  string Name { get; set; }
}

interface IExtended : IBase
{
  bool IsChecked { get; set; }
}

Then I use base interface as a parameter in a delegate function that is also a parameter to class constructor, like this:

public class SomeClass
{
  private IBase _model;
  private Func<IBase, string> _handler;

  public SomeClass(IBase model, Func<IBase, string> handler)
  {
    _model = model;
    _handler = handler;
  }

  public string ExecuteHandler()
  {
    return _handler(model);
  }
}

Interface implementations:

public class BaseImplementation : IBase
{
  int Id { get; set; }
  string Name { get; set; }

  public BaseImplementation(int id, string name)
  {
    Id = id;
    Name = name;
  }
}

public class ExtendedImplementation : IExtended
{
  int Id { get; set; }
  string Name { get; set; }
  bool IsChecked { get; set; }

  public BaseImplementation(int id, string name, bool isChecked)
  {
    Id = id;
    Name = name;
    IsChecked = isChecked;
  }
}

Intended use:

BaseImplemetation baseModel = new BaseImplementation(1, "base");
ExtendedImplemetation extendedModel = new ExtendedImplementation(2, "extended", true);

SomeClass someClass1 = new SomeClass(baseModel, (IBase arg) => {
  Console.Write("Remember, " + arg.name + ", YOLO!");
});

SomeClass someClass2 = new SomeClass(extendedModel, (IExtended arg) => {
  Console.Write(arg.name + ", YOLO! You're " + (arg.IsChecked) ? "checked!" : "not checked!");
});

string res1 = someClass1.ExecuteHandler();
string res2 = someClass2.ExecuteHandler();

But that ( doesn't work, even though implementation of IExtended would necessarily implement everything that is defined by IBase interface. Why is that so and how would I bypass this and get the result I want?

EDIT:

I think I got it now.

I thought that Func<IBase, string> is equal to Func<IExtended, string> because IExtended of course implements everything that IBase does, so there should be no problem, right? Implementation as I wanted it to be and is listed in my example would of course work just fine.

BUT! The problem is that someClass2 can't be constructed like that because, as @Servy mentioned, delegate function could do something like this:

SomeClass someClassWrong = new SomeClass(baseModel, (IExtended arg) => {
  if (arg.IsChecked) {
    // gotcha, baseModel doesn't have IsChecked property!
  }
});

EDIT 2:

Thank you everybody for you help and sorry for constant editing and giving wrong example sof what I want :D

DekiChan
  • 375
  • 1
  • 4
  • 14

3 Answers3

3

But that doesn't work, even though implementation of IExtended would necessarily implement everything that is defined by IBase interface. Why is that so and how would I bypass this and get the result I want?

When SomeClass invokes that delegate it might not actually pass an IExtended instance. It's allowed to provide any IBase instance as the parameter, so if it provides one that doesn't implement IExtended, then what would you expect your delegate to do?

If SomeClass is always going to pass an IExtended instance, then modify the delegate it accepts in its constructor accordingly, so that the callers always know they're getting an IExtended instance as a parameter.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • The delegate should always use only methods defined in IBase (so in this example IBase.SomeMethod()) inside SomeClass. Other methods defined in IExtended are used in other parts of application, but in this specific case I'd like to use existing delegate with IExtended implementations also. – DekiChan Dec 19 '16 at 16:14
  • @DekiChan Like I said, either the actual objects you pass to the delegate when invoking it are always `IExtended` instances, in which case, you need to change the delegate you're accepting, or they aren't, and the code providing the delegate can't assume that they're `IExtended` instances. You can't provide `IBase` objects that aren't `IExtended` and get `IExtended` behavior. – Servy Dec 19 '16 at 16:14
  • I expanded my question with, hopefully, clearer explanation on what I'd like to do. – DekiChan Dec 19 '16 at 16:29
  • @DekiChan In your example in both cases you're providing a method that accepts *no* arguments and returns a `string`. If that's what you want to support, then that's what your delegate signature should be. – Servy Dec 19 '16 at 16:35
  • I see that I still haven't explained what I would really like to do. But I think first I need a good nights sleep because obviously my brain doesn't function that well currently. But thank you so far, I'll get back to it tomorrow. – DekiChan Dec 19 '16 at 16:58
  • Yeah, you were right from the start, but I didn't understand what you were telling me :) – DekiChan Dec 19 '16 at 17:59
0

You can simply define a delegate that knows the IBase is really an IExtended:

SomeClass someClass = new SomeClass((IBase arg) => { (arg as IExtended).DoSomethingOnlyExtendedKnowsAbout(); });

This is potentially unsafe, but if you somehow can enforce that the arg passed to that specific lamda will always be an IExtended then there is no harm. You could also provide a safety mechanism in the lambda itself and manage it accordingly up the call stack:

SomeClass someClass = new SomeClass((IBase arg) => { (arg as IExtended)?.DoSomethingOnlyExtendedKnowsAbout(); });
InBetween
  • 32,319
  • 3
  • 50
  • 90
  • 2
    If you're *always* going to pass in an `IExtended` then you should just be changing the delegate signature. – Servy Dec 19 '16 at 16:35
  • @Servy Not knowing the full scenario it's not always the case. Maybe this concrete `SomeClass` will always receive `IExtended` arguments but `IBaseClass` wont in general. [This question](http://stackoverflow.com/a/39821458/767890) and answer demostrates how this can be type safe if constructed correctly. – InBetween Dec 19 '16 at 16:42
  • Thank you for your help, I was asking the wrong question when I tried to make it clearer - I again edited it and explained everything. I think now it is clear what I wanted and what was wrong :) – DekiChan Dec 19 '16 at 17:54
0

I don't see the problem. Based on the code you have, the following works as intended:

public class SomeClass
{
    public SomeClass(Func<IBase, string> handlerFcn)
    {
        // something gets done
        this.Handler=handlerFcn;
    }
    public Func<IBase, string> Handler { get; set; }
}

public static class Program
{
    static void Main(string[] args)
    {
        var s1 = new SomeClass((x) => x.SomeMethod());
        var xt = new ExtendedClass();
        var result = s1.Handler(xt);
        // result = "yolo extended edition!"
    }
}

I think you were trying to use the concrete class ExtendedClass in the lambda definition and that won't work unless you define it as a closure.

Community
  • 1
  • 1
John Alexiou
  • 28,472
  • 11
  • 77
  • 133