Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Extension classes with Interfaces #3357

Closed
grwGeo opened this issue Jun 6, 2015 · 18 comments
Closed

[Proposal] Extension classes with Interfaces #3357

grwGeo opened this issue Jun 6, 2015 · 18 comments

Comments

@grwGeo
Copy link

grwGeo commented Jun 6, 2015

I believe that type extensibility (as in extension methods) is a very useful feature that should be viewed as part of a healthy OOP design and not just as compiler deception or syntactic sugar.

Type extensibility can be expanded to the much anticipated extension properties, extension events, indexers etc and even Interface implementation. This last one would be particularly useful should we need to group types owned by others, in interfaces owned by us (or others).

The main advantage of type extension is the ability to intervene high up in an inheritance chain, adding extra functionality to a multitude of types with just one stroke (e.g. imagine adding a custom property to all windows forms controls with just a few lines of code). And with the addition of implementing interfaces this can be a very powerful tool.

I have personally indirectly implemented extension properties and events, so it should be logically consistent to implement these.

A syntax for all these could be similar to partial classes and this could replace current syntax for extension methods as well. So something like this (interface optional of course):
(Here is code)

public extension class OtherOwnersNamespace.OtherOwnersClass : ISomeInterface
{
    private int _myExField = 0;
    public int MyExProperty { get { return _myExField; } }

    public event EventHandler<EventArgs> MyExEvent;  //Could add accessors too.

    internal void MyExMethod() { }
    protected virtual void MyExVirtualMethod() { } //Could be overridden in an extension of an OtherOwnersDerivedClass  
    public static void MyExStaticMethod() { }

    public string this[object someObject] { get { return someString; } }


    void ISomeInterface.SomeInterfaceMethod(){}
}

The 'this' keyword should give us all properties of the original class plus our extension class.
The scope and visibility of the extension class and its members would be the same as if the class was defined normally without the 'extension' keyword.

Current syntax could also remain as an alternative to avoid unnecessary lines of code for cases where (for example) lots of different single extension methods for different types are defined and gathered in one static class.

@svick
Copy link
Contributor

svick commented Jun 6, 2015

I don't think extension interfaces can be implemented reasonably well without CLR support.

The only way I can imagine it would be implemented is if casting to that interface returned some wrapper object. But that would mean ReferenceEquals wouldn't work reliably for such objects. E.g.:

var c = new OtherOwnersClass();                  // c is the original object
ISomeInterface i = c;                            // i is the wrapper object
Console.WriteLine(object.ReferenceEquals(c, i)); // writes "False"

@grwGeo
Copy link
Author

grwGeo commented Jun 7, 2015

I believe that since class extensibility is logically consistent, it deserves to be part of OOP and therefore have full CLR support (not compiler deception). This is what my proposal is aimed at.

@grwGeo
Copy link
Author

grwGeo commented Jun 7, 2015

Readers should understand that this is a two part proposal:

  1. Simple class extensibility.
  2. Interface implementation extensibility.
    It can be implemented in these two stages and the first stage could be implemented gradually (e.g. an extension class might not support extension events or indexers to start with).

@grwGeo
Copy link
Author

grwGeo commented Jun 7, 2015

Another useful application of interface extensibility is when the interface might require implementation of an already existing member.
E.g. all controls (or base controls) that already have a 'Caption' property might be extended to implement an 'ICaption' interface (if they don't implement one). This would allow us to group them into ICaption types and work collectively with their Caption property.
This would only require the first line of the proposed syntax and with such one-liners we could "register" a multitude of different base controls (i.e. different hierarchies).

@ufcpp
Copy link
Contributor

ufcpp commented Jun 8, 2015

I' m going to implement the same purpose but somewhat different approach by using a Roslyn code fix provider. I call it "MixinGenerator". (but at this time, this repository contains only test cases: some pairs of an original source and the expected generated result, no implementation.)

The code fix, for instance, will generate a code from

using Mixins.Annotations;

namespace MixinGenerator
{
    [Mixin]
    public struct X
    {
        public string PublicValue => null;

        [Protected]
        public string ProtectedValue => null;

        [Private]
        public string PrivateValue => null;
    }

    public class Sample
    {
        X _x;
    }
}

to

    public class Sample
    {
        X _x;

        public string PublicValue => _x.PublicValue;

        protected string ProtectedValue => _x.ProtectedValue;
    }

If a Mixin struct implements an interface, the code fix will generate a class which implements the same interface.

@paulomorgado
Copy link

Just for the benefit of all,. there really isn't such a thing as partial classes. There are partial class definitions; meaning, partial definitions of classes.

@grwGeo
Copy link
Author

grwGeo commented Jun 10, 2015

You are right for 'partial classes' but I think that for 'extended classes', philosophically speaking, you could argue both ways.

I.e. they are 'an extended definition of a class' but they are also an almost (but not fully) independent class of their own (and they would be in cases where they do not refer to any of the original members). Another way of looking at it is that they are 'portions of classes' as almost independent data constructions.

So the extended 'portion' is attached to the original part.

@grwGeo
Copy link
Author

grwGeo commented Jun 10, 2015

Extension constructors should also be possible with the restriction that they call an original constructor and of course they do not conflict with the signature of other existing constructors. Something like:

public extension class OtherOwnersNamespace.OtherOwnersClass : ISomeInterface
{
public OtherOwnersClass(an original set of params, plus a new set of params)
: this(an original set of params){ }
}

@Przemyslaw-W
Copy link

@grwGeo what advantage has such extension ctor over factory method?

@grwGeo
Copy link
Author

grwGeo commented Jun 11, 2015

@Przemyslaw-W The whole aim is to treat an extended type just like a normal class, with all the syntax that pertains to a class and all related intellisense.
Creating such extension constructors would constrain consumers of the class to initialize instances in a specific way, as long as it is consistent with the initialization of the original class.

@grwGeo
Copy link
Author

grwGeo commented Jun 12, 2015

I don't know any F# but looking online today I am under the impression that it has advanced type extensibility features (more than C#). Is this true?

@HaloFour
Copy link

F# type extensions are really just syntax for either amending a partial class defined in the same module, or for defining extension methods. C# has those same features.

@grwGeo
Copy link
Author

grwGeo commented Jun 12, 2015

So in F# extending fields (properties?) or other members apart from methods in a different assembly (for example) is not possible.

Thanks for the clarification.

@grwGeo
Copy link
Author

grwGeo commented Jun 24, 2015

It might also be possible for extended controls to have full VS designer support. This would be an alternative to inheriting and as described above preferable in cases where we intervene high up in the hierarchy. This might be the basis for the CLR version of the Dependency Properties pattern in WPF.

@GSPP
Copy link

GSPP commented Jun 24, 2015

So you want to add new fields to existing types, right? This is an important capability. Often, you cannot define the necessary fields directly on some type for reasons of modularity.

For example, WinForms controls often need user data attached to them. They expose the object Control.Tag property which can be used to attach it. That's very specialized, hacky and allows only for a single tag consumer.

For example if your app deals with customers there will be many aspects. The CRM will want different data than some billing application. But ideally all modules would deal with the same customer object instance. For modularity reasons we would not want to add all CRM and billing fields to the customer. Those should be details of the respective modules.

Imagine there were 10 modules operating on customer objects. What a big ball of mud it would be to attach all required data to the customer object. It would need to have an assembly reference to all modules and all modules would need to reference the customer class. That's cyclic, and a mess.

@grwGeo
Copy link
Author

grwGeo commented Jun 25, 2015

@GSPP The idea behind extension is not about breaking a class into constituent classes (like partial definitions of classes). The idea behind extension is that the original class is generally unaware of the extensions. These extensions can affect the original class only within their scope of visibility. The original class does not need a reference to the assembly or namespace that extends it, only the other way around.

If your billing module needs to extend the original class, what you would do with what the language provides today is probably define a new class (like CustomerBillingData) and associate an instance of that class with an instance of the Customer class. This class could have its own data, its own logic but it can also interact with the Customer class' data and logic too. The Customer class though would not be aware of the CustomerBillingData class and would not be the cause of any interaction.

Now if you had the extension feature available and IF you believed that conceptually the CustomerBillingData class should have been part of the Customer class then you could achieve the exact same thing by extending the Customer class. This would save you the trouble of associating the instances of Customer-CustomerBillingData plus it should make it conceptually easier to use just one class instead of two (i.e. customer.Order would be more appropriate than customer.CustomerBillingData.Order or customerBillingData.Order). The extension would only be visible from within the extension assembly (same as with CustomerBillingData).

Also if you were to serialize your extended data on a database, the Entity Framework could interpret extension classes as separate tables with a 1-1 relationship with the original class.

@GSPP
Copy link

GSPP commented Jun 25, 2015

@grwGeo You could define a CustomerBillingData class but you can't pass it through code that only expects a Customer. You can pass in the Customer but you can't get the CustomerBillingData back out. An example would be some kind of session.

With extension fields you could do that.

@gafter
Copy link
Member

gafter commented Apr 28, 2017

Issue moved to dotnet/csharplang #514 via ZenHub

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants