views:

327

answers:

6

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?

A: 

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
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
A: 

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
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
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
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
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
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
ShuggyCoUk
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
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
So we agree that had the framework included IControl interface this would not be a problem in this case?
Goran
What about adding a "Control UnderlyingControl { get; }" member to ISomeInterface? That would at least solve one direction.
David Schmitt
Agreed Goran - IControl would indeed solve this problem. It just led me to this theory :)
Rory Becker
A: 

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
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
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
+1  A: 

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
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
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
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
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
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 headTo quote MS "Everything starts with -100"
ShuggyCoUk
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
A: 

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

Oorang
A: 

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.

Zidad