8

Intro

This question prompted by Marc Gravell's suggestion that I post new language feature suggestions to this site to gather general opinion on them.

The idea is to gather if they might be helpful or maybe there is already another way to achieve what I am after.

Suggestion (Constrained Types)

A normal variable declaration in VB.Net is written thus:

Dim SomeVariable as SomeType

I suggest allowing the following form(s)

Dim SomeVariable1 as {SomeType, ISomeInterface}
Dim SomeVariable2 as {SomeType, ISomeInterface, ISomeOtherInterface}

This syntax is borrowed from Vb.Net's style of constraining Generics

Why allow this?...What is it good for?

Well the specific case I originally thought of was to define a particular subset of controls. I wished to create an interface for a series of Control factories which would provide controls based on some business rules.

The consumer of these controls would require, through the interface, that all controls created, should also implement some series of interfaces (only one in my case) which gave all these controls additional facilities not found generally in normal controls.

It is worth noting that the following currently does not work.

Public Interface ISpecialControl
End Interface

Public Interface IControlProvider
    Function CreateControl(Of T As {Control, ISpecialControl})() As T
End Interface

Public Class SpecialTextBoxProvider
    Implements IControlProvider
    Public Function CreateControl(Of T As {Control, ISpecialControl})() As T Implements IControlProvider.CreateControl
        Return New SpecialTextBox
    End Function
End Class

Public Class SpecialTextBox
    Inherits TextBox
    Implements ISpecialControl
    Public Sub New()

    End Sub
End Class

I think this translates to C# as:

public interface ISpecialControl
{
}

public interface IControlProvider
{
    T CreateControl<T>()
        where T : Control, ISpecialControl;
}

public class SpecialTextBoxProvider : IControlProvider
{
    public T CreateControl<T>()
        where T : Control, ISpecialControl
    {
        return new SpecialTextBox();
    }
}

public class SpecialTextBox : TextBox, ISpecialControl
{
}

The attempt to return "New SpecialTextbox" fails due to an inability to cast SpecialTextbox to T.

"Value of type 'MyApp.SpecialTextBox' cannot be converted to 'T'"

I realize that my factories could be allowed to return simple controls and I could check at runtime if they implemented ISpecialControl, but this would yield runtime problems which I would rather check at compile time since it is a logical possibility even if not currently a practical one

Update: The idea is that these factories could sit in external (perhaps even third party) assemblies and could take a dependency on any control libraries they wanted, creating and returning derivatives of these controls which also implemented ISpecialControl.

These assemblies could be located through self-configuring-reflection (Reflection on first pass followed by configuration, which is then used on further runs) and used without any knowledge by the calling assembly on what dependancy these controls would have taken.

It does require that these factories are constructable without passing information about the control they are expected to call, since that would defeat the point.

So what do you think... Would this be useful?... Is there a better way to achieve this? Is there already a way to achieve this?

Lucas
  • 17,277
  • 5
  • 45
  • 40
Rory Becker
  • 15,551
  • 16
  • 69
  • 94
  • Could you provide a sample in C#? For those who don't know VB, it would be much easier to read, and would have the added benefit of syntax coloring. – Hosam Aly Feb 16 '09 at 12:58
  • I fixed the formatting for the C# part. Tabs shouldn't be used, so I replaced them with spaces. I've also modified `SpecialTextBox.New()` to return void. (I guess that's what you intended. If it isn't then I apologize.) – Hosam Aly Feb 16 '09 at 13:31
  • Thanks for the help Hosam. Looks much better :) – Rory Becker Feb 16 '09 at 13:42
  • I removed the unnecessary default constructor in C#, it had incorrect syntax anyway. SpecialTextBox() is used instead of New(). ;) – Lucas Jun 01 '09 at 14:31

7 Answers7

1

I think, whilst superficially useful this sort of thing is better handled by one of the two:

  • Duck Typing via dynamic (coming in VS2010) for vastly more flexibility albeit removing type safety.
  • Generic composition

Your original example requires neither and can be easily made to work like so:

public interface IControlProvider<T>
    where T : Control, ISpecialControl
{
    T CreateControl();
}

public class SpecialTextBoxProvider : IControlProvider<SpecialTextBox>
{
    public SpecialTextBox CreateControl()
    {
        return new SpecialTextBox();
    }
}

In fact given that most controls have a default public constructor you can make it even easier:

public class ControlProvider : IControlProvider<T>
    where T : Control, ISpecialControl, new()
{
    public T CreateControl()
    {
        return new T();
    }
}

var control = ControlProvider<SpecialTextBox>.CreateControl();

Given the additional restriction on calling code not taking a reference dependency on SpecialTextBox directly this won't work.

ShuggyCoUk
  • 36,004
  • 6
  • 77
  • 101
  • Your final line passes SpecialTextBox in as the generic type. It sort of defeats the point of a factory if you have to tell it what it has to create is such a direct manner. the requires the calling code to have a direct dependency on SpecialTextBox – Rory Becker Feb 17 '09 at 08:43
  • I have updated the question to clarify that the calling code should take no additional dependencies when new factories are added through additional assemblies. – Rory Becker Feb 17 '09 at 08:52
  • Right, with that restriction this is more complex, but I would suggest that this is increasingly into the realms of too little benefit for the additional complexity it causes. – ShuggyCoUk Feb 17 '09 at 09:02
  • What complexity do you see Constrained types causing. Seriously... if it were to suddenly appear in the next version.. Would you understand it? Would it cause you any problems? I can appreciate that not all would use it, but that's not the point. It's a very simple syntax addition. – Rory Becker Feb 17 '09 at 09:08
  • It's not the syntax, it's how it operates. It would change the method resolution rules for starter (much more complex). This would impact dynamic (since it is designed to have identical runtime behaviour as compile time). This is just off the top of my head To quote MS "Everything starts with -100" – ShuggyCoUk Feb 17 '09 at 09:16
  • it would make intellisense much more complex. refactoring tools too. The compiler would actually be much more complex since it would have to carry more info around on each variable (as opposed to the current generics where it can synthesize a temp type per generic class). Really this is a big change – ShuggyCoUk Feb 17 '09 at 09:20
0

It would seem a useful language feature. It's possible to have method parameters whose type is unknown other than its compliance with multiple constraints, but there's no way to store such a parameter in a field in such a way that it can ever be passed to such a function, or even typecast to be passed.

There is another way, however, of achieving the basic result you're after, though it's a little clunky. See my question Storing an object that implements multiple interfaces and derives from a certain base (.net) where offer up the best approach I've been able to formulate.

Community
  • 1
  • 1
supercat
  • 77,689
  • 9
  • 166
  • 211
0

It is more "constrained variables" than "constrained types" - but definitely interesting. As with generic constraints, there are probably a few minor niggles with resolution if there are conflicting members, but the same workarounds would presumably apply as with generic constraints.

Of course, the pragmatic view is that you can already cast etc, but then you need to keep the two references up-to-date with each-other...

hmm... interesting.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I have not tried as yet, but my understanding is that if I Cast as I return (using 'Trycast' or 'As') that this could (and in this case) would fail at runtime. – Rory Becker Feb 16 '09 at 13:28
0

How about using interface composition?

You have

interface IA;
class B;

if it exists use interface for class B and make a composite

interface IB;
interface IAB : IA, IB;

modify class

class C : B, IA

to implement composite interface (that should not present a problem)

class C : B, IAB

And you can use interface IAB as your constraint type.

Goran
  • 6,798
  • 9
  • 41
  • 57
  • Does not work as the 2 things that I require are not both interfaces. one is an actual Class (Control in this case). I could totally reimplement an IControl interface but I could not add items of this type to the Controls collection of a form. – Rory Becker Feb 16 '09 at 13:15
  • This forces me to cast at runtime which then results in potential runtime errors which are hard(er) to trap compared to compile time errors. The implementors of IControlProvider might be 3rd parties and therefore I would like to force them to a contract of {Control + ISpecialControl} – Rory Becker Feb 16 '09 at 13:16
  • Why does an actual implementation of an interface matter? Why would you need to cast from interface? The purpose of interfaces is to specify a contract and anything implementing said interface should do. If that is not the case than I suspect there are other problems with the code you need to solve. – Goran Feb 17 '09 at 07:17
  • This example is about producing controls. Controls are useless unless they are added to a form. This can only be done by adding them to a controls collection which only accepts controls. Currently I can only guarantee return types of either Control OR ISpecialControl. I would like to ensure both – Rory Becker Feb 17 '09 at 08:28
  • 2
    This is basically a flaw in the design of the WinForms API. Not allowing IControl was simple laziness on their part (it's much easier doing widget libraries when you control the base class and can put in internal methods for your own usage). I don't think flawed API design merits this change – ShuggyCoUk Feb 17 '09 at 09:11
  • £*%@#&! annoying though I must admit – ShuggyCoUk Feb 17 '09 at 09:13
  • Yeah but this simple change would allow you to negate this problem here in the Winforms API and in any other API that cropped up elsewhere with a similar issue. I still think it's worth it. – Rory Becker Feb 17 '09 at 09:16
  • I disagree for the reasons in the comments on my answer. I think it's agree to disagree since the odds on them adding this are very remote. To my knowledge no experimental languages in 'net have added anything like it either... – ShuggyCoUk Feb 17 '09 at 13:56
  • So we agree that had the framework included IControl interface this would not be a problem in this case? – Goran Feb 17 '09 at 14:19
  • What about adding a "Control UnderlyingControl { get; }" member to ISomeInterface? That would at least solve one direction. – David Schmitt Feb 17 '09 at 14:39
  • Agreed Goran - IControl would indeed solve this problem. It just led me to this theory :) – Rory Becker Feb 17 '09 at 16:08
0

I don't understand what you're proposing, I think that's my problem.

However, it sounds to me as you want to return a control that implements a specific interface.

Why can't you just say that the method returns that specific interface? It sounds to me as you're saying that "I want to return classes that descend from Control, and that also implements interface ISomeInterface".

Perhaps the solution is to extract the parts of Control you need into your own interface, and specify that you want to return objects that implement both?

Like this:

interface IControl { ... }
interface ISomeInterface { ... }

interface IControlInterface : IControl, ISomeInterface { ... }

This last interface would specify that you need an object that implements both the base interfaces, which is what it sounds like you want, except that you want one of the requirements to be a base class, instead of an interface.

I think you can solve this just by using interfaces.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • If I just return the object as Interface, I will be unable to add the returned object to a controls collection (on a form) without casting at runtime. If I cast at runtime, I risk the creator of the factory not having descended it's output from control. – Rory Becker Feb 16 '09 at 13:26
  • This can be caught but only at runtime.. I am looking to be able to provide a contract which guarantees suitability at compile time. – Rory Becker Feb 16 '09 at 13:26
0

Is this substantially different from extension methods? Why not just extend the controls?

Oorang
  • 6,630
  • 1
  • 35
  • 52
0

edit: Oh wait I didn't read correctly, this is about constraining it to 2 types, not a choice 2 types. I think you could solve it similar to the class mentioned below though...

I've experimented with a class once to simulate this behavior:

public class Choice<T1, T2> 
{
    private readonly T1 value1;
    private readonly T2 value2;

    public Choice(T1 value)
    {
        value1 = value;
    }

    public Choice(T2 value) 
    {
        value2 = value;
    }

    public static implicit operator T1(Choice<T1, T2> choice) 
    {
        return choice.value1;   
    }

    public static implicit operator T2(Choice<T1, T2> choice) 
    {
        return choice.value2;   
    }

    public static implicit operator Choice<T1, T2>(T1 choice) 
    {
        return new Choice<T1, T2>(choice);
    }

    public static implicit operator Choice<T1, T2>(T2 choice) 
    {
        return new Choice<T1, T2>(choice);
    }

    public T1 Value1
    {
        get { return value1; }
    }

    public T2 Value2
    {
        get { return value2; }
    }
}

which allows you to create constructs like this:

[TestMethod]
public void TestChoice() 
{
    TestChoice("Hello");
    TestChoice(new []{"Hello","World"});
}

private static void TestChoice(Choice<string[], string> choice)
{
    string value = choice;
    string[] values = choice;

    if (value != null)
        Console.WriteLine("Single value passed in: " + value);

    if (values != null) {
        Console.WriteLine("Multiple values passed in ");
        foreach (string s in values)
            Console.WriteLine(s + ",");
    }
}

Downside of the implicit operators is that it doesn't do implicit casting to an interface (it does work casting from an interface type), so passing in an IEnumerable<T> instead of a string[] wouldn't work when trying to cast the contained value back from Choice<IEnumerable<T>, T> to IEnumerable<T>.

Generally I would prefer just to use overloads/interfaces BTW, so don't get me wrong here :)

It would be more useful in cases where you use properties (and can't use overloading for that reason), something like a DataSource property that only allows list<string> or list<ListItem> for example.

Wiebe Tijsma
  • 10,173
  • 5
  • 52
  • 68