dotnet / csharplang

The official repo for the design of the C# programming language
11.49k stars 1.02k forks source link

Anonymous Interface Objects #4301

Open khalidabuhakmeh opened 3 years ago

khalidabuhakmeh commented 3 years ago

Anonymous Interface Objects

Summary

Give C# developers the ability to instantiate anonymous objects that implement a specific interface without first implementing a concrete type.

Motivation

In scenarios where developers need to stub a type for unit tests, it becomes increasingly tedious to create classes that are essentially throw away. They add to the complexity of the project and ultimately increase the noise/signal ratio.

Detailed design

public interface IClient
{
    string Name { get; }
}

// single interface
var client = new IClient {
    Name = "Khalid"
};

We can also implement multiple interfaces on an anonymous type.

public interface IClient
{
    string Name { get; }
}

public interface ICustomer
{
    string AccountNumber { get; }
}

// multiple interface
var client = new IClient, ICustomer {
    Name = "Khalid",
    AccountNumber = "8675309"
};

Method implementations can be implemented using lambda methods.

public interface ICashier
{
    decimal CheckOut(Cart cart);
}

var cashier = new ICashier {
   CheckOut = (c) => cart.Total() * 0.2d;
}

Drawbacks

Alternatives

Create stub types for every new scenario you need an interface.

Use Kotlin. Here is a working example where the khalid instance is an anonymous object that happens to implement the IGreeting interface.

image

    interface IGreeting {
        val name: String
    }

    class Default(override val name: String) : IGreeting

    fun main() {

        val csharp = Default("C# Developer")
        // an anonymous object
        // that implements IGreeting
        val khalid = object: IGreeting {
            override val name: String
                get() = "Khalid (You're Awesome)"
        }

        var folks = listOf(csharp, khalid)
        folks.forEach { println("Hello ${it.name}!") }
    }

Unresolved questions

Design meetings

jnm2 commented 3 years ago

Previously discussed at https://github.com/dotnet/csharplang/discussions/738 (see HaloFour's comment too)

Unfortunately the first time this was proposed (https://github.com/dotnet/roslyn/issues/13#issuecomment-173413281):

We have no expectation of ever doing anything like this.

For the record, that comment has 47 downvotes, including mine. I would love this feature.

The alternatives section could mention Moq and NSubstitute. (I like NSubstitute better because it has less ceremony.) NSubstitute is really not a bad way to build up just the parts of the interface you care about for the test, and there would still be times I would prefer NSubstitute over anonymous interface implementation. But I also want this feature in non-test scenarios.

HaloFour commented 3 years ago

See also:

https://github.com/dotnet/csharplang/discussions/2517 https://github.com/dotnet/csharplang/discussions/4148

JasonBock commented 3 years ago

I would personally want to see the C# team work on other features, like making the type system more expressive and extensible (concepts or roles or whatever it ends up being called :) ). This is something that arguably mocking frameworks can already do (and it doesn't require Reflection either. My recent update to Rocks moved everything to being source generator based), or use source generators to accomplish something very similar (e.g. https://github.com/devlooped/avatar).

HaloFour commented 3 years ago

I disagree that mocking frameworks are a replacement for this feature anymore than manually writing an adapter would be. They are simply not capable or expressive enough and would generally require substantially more code at the call site. A source generator provided adapter could not enclose the state within the method in which the adapter is used, like an anonymous implementation would.

As for the question of priority, that's up to the team, but championing an issue does not establish its priority or precedence over other features. I'd also argue that this feature is substantially less complicated than concepts/roles/shapes/whatever and has absolutely no runtime requirements and could be implemented in a relatively short amount of time, especially since the compiler is already quite capable of producing unnamed display classes for the sake of implementing closures today.

JasonBock commented 3 years ago

Yes, this feature would be less complicated than concepts/roles/shapes/etc. It would still require work, and FWIW there are ways to already achieves the end result. They're not as elegant as what this feature would provide, but again, is it worth it.

I should be clear, I'm not saying "dismiss the idea entirely".

dstarkowski commented 3 years ago

Unit tests are imo one of the most neglected areas in the language. I don't recall any feature in last few versions of C# that was targeted specifically to make unit tests or mocking easier.

Hard to convince someone to write tests, if the test is more complex and obscure than the actual code. Any investment here would be greatly appreciated.

GalaxiaGuy commented 3 years ago

I just like to highlight one use for this that is nothing to do with unit testing (and nothing to do with mocking conceptually, even if a mocking framework might solve the technical issue) is one that HaloFour mentioned (a little too subtley):

Improved interop with Java: https://github.com/dotnet/csharplang/discussions/2517

Working with Xamarin Android, I write code every few days that could take advantage of this.

0x0737 commented 3 years ago

I think there should also be a way to specify is it a class or a struct And wouldn't it be better if it wasn't limited to just interfaces? Why not allow any type declarations be inlined into new()?

P.S. Not literally "any", of course. E.g. you cannot use anonymous enums etc. But at least, anonymous classes inheriting normal classes can be done.

Phantonia commented 3 years ago

@0x0737 Do you mean that one should be able to specify if the anonymous type should be generated as a class or struct? I don't think generating it as a struct would have any advantages due to it being boxed (so heap allocated) anyway because it could only be used via the interface.

0x0737 commented 3 years ago

@Phantonia I meant I think there is no need to restrict this proposal to anonymous interfaces only. I think it is better to allow to define a local type and create instances of it, something like java's anonymous classes. This is very useful for unit testing.

Something like this:

ClassA obj = new class : ClassA()
{
    public override void SomeMethod() { /*some code*/ }
};

or with extracted declaration

class ClassB : ClassA
{
    public override void SomeMethod() { /*some code*/ }
}

ClassB obj = new();
leandromoh commented 3 years ago

F# has this feature long time... it is called object expressions

quixoticaxis commented 3 years ago

F# has this feature long time... it is called object expressions

Yeah, and the same-ish syntax would look fine in C#. I had the question why it's not possible in C# from my first day with F#.

FANMixco commented 3 years ago

This will also simplify Android for .NET development in the long term. I have migrated 10 libs and it's a headache because we don't have a similar feature.

leandromoh commented 3 years ago

C# recent features are trying to remove boilerplate code (e.g., top-level statements, records, target-typed new expressions, null-coalescing assignment, etc) so this one would be VERY useful too...

acaly commented 3 years ago

Came from another discussion which let me think about inlining of delegates. Currently it seems (please correct me if I'm wrong) the runtime cannot inline any closure passed into Linq methods, so if there is a hot spot in the Linq method's loop, and the closure is simple enough, then we are paying much to function call overhead. The only way to force inlining (according to sharplab.io) is to use struct implementation of interface like this:

    private interface IAddInt
    {
        int Invoke(int a, int b);
    }

    private struct CAddInt : IAddInt
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int Invoke(int a, int b)
        {
            return a + b;
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static int M1<T>(int[] a, T closure) where T : struct, IAddInt
    {
        return closure.Invoke(100, a[0]);
    }

    public static int M2(int[] a)
    {
        return M2<CAddInt>(a, default); //Here everything will be perfectly inlined and no function call is involved.
    }

This is basically how C++ handles lambda expressions. Since C# already supports lambda in a different way, I think the above code can really benefit from the anonymous interface implementation feature instead. For example, we could write:

    private interface delegate int AddIntDelegate(int a, int b);

    private static int M1<T>(int[] a, T d) where T : AddIntDelegate
    {
        return d(a[0], 100);
    }

    public static int M2(int[] a)
    {
        return M1(a, (x, y) => x + y); //The compiler will generate a struct implementing AddIntDelegate
        //or alternatively,
        //return M1(a, new struct { int Invoke(x, y) => x + y; });
    }

Here more than one feature is needed for C# to support it like this (if ever). Not necessarily, the opaque parameter feature (currently as rejected) would also help to make the code looks nearly identical (and being equally powerful) to C++ lambda:

    private interface delegate int AddIntDelegate(int a, int b);

    private static int M1(int[] a, some AddIntDelegate d)
    {
        return d(a[0], 100);
    }

    public static int M2(int[] a)
    {
        return M1(a, (x, y) => x + y);
    }
quixoticaxis commented 3 years ago

@acaly C# delegate has a load of functionality not presented in the unnamed C++ closure objects, so what you want can be usable either if you can fully inspect the callee at compile time, or if the functionality of interface delegate is very limited.

bernd5 commented 3 years ago

In C++ a compiler is not required to inline lambdas but allowed to do so if the behaviour does not change. And this is true for csharp as well.

acaly commented 3 years ago

@quixoticaxis I agree that C# delegates are more powerful than C++'s std::function, especially when doing events, but it's not so good at doing something like performance-critical Linq. (I should say the C++ std::function can't do this either. It is achieved by the template magic.) Linq was originally written with delegates. That's probably the best way to do it in C#. However, with recent focus on improving C#'s performance (Span<T>, hardware intrinsics, static delegate, etc.), I think it's not impossible to have another set of Linq API in C++-style, i.e., replaceing the delegate with a generic parameter. Of course it will have issues like larger binaries, but it's a trade-off programmers can do themselves.

if you can fully inspect the callee at compile time

The runtime can inspect everything it likes at (just-in-time) compile time. Theoretically, it even have the option to inspect every destination of a virtual call site and recompile, which is more powerful than C++ compilers. I don't know what is the current difficulty for the JIT compiler to a inline delegate call with a known target, but asking C# compiler to translate a lambda to a generic method on struct (constrained prefix) seems to greatly help the JIT compiler to optimize it.

In summary, the issue I wanted to raise is to improve the performance of using a delegate with known target. One strategy is to ask runtime JIT to try harder in optimizing it and inlining. The other is to ask C# to generate codes that are easier for runtime to optimize (and I think it fits well in this "Anonymous Interface Object" proposal). Both need significant amount of work, but I really hope we can have one in the future.

acaly commented 3 years ago

In C++ a compiler is not required to inline lambdas but allowed to do so if the behaviour does not change. And this is true for csharp as well.

I don't think C# and .NET runtime is doing the same as well today. For example, the code below will call the empty function through an indirect call in every iteration, even if the static local method itself is inlined:


    static void Repeat(Action<int> f)
    {
        for (int i = 0; i < 100000000; ++i)
        {
            f(i);
        }
    }
    Repeat(i => { });

On C++, all compilers today can eliminate the equivalent into a no-op.

quixoticaxis commented 3 years ago

@acaly I'm not saying it's impossible, but AFAIK 1) the current JIT cannot do it reasonably for Linq; 2) and if you propose language extension (which, IMHO, should guarantee some great performance improvements to futher bloat, yet again IMHO, already overbloated language), than it seems it can be done with function pointers.

acaly commented 3 years ago

@quixoticaxis

it seems it can be done with function pointers

Actually I have thought about this already, but using delegate* still gives an indirect call (mov eax XXXXXXXX; call eax). Although I expect it to be much better than using Delegate, it still prevents the JIT from inlining the callee, which is the true power of C++'s template magic.

rashadrivera commented 3 years ago

If we are going to enhance the C# language, then we must respect its syntax. From many of the examples proposed to date, I see an issue where methods are treated as delegates. This is unnatural for C# and should not be implicitly allowed. Otherwise we introduce syntax that is native to languages like JavaScript; which I'm sure we are all against.

With that said, the below code should not be allowed (as mentioned in the Design Details of this issue)

public interface ICashier
{
    decimal CheckOut(Cart cart);
}

var cashier = new ICashier {
  // This is an implicit change to a delegate via the use of a lambda expression
   CheckOut = (c) => cart.Total() * 0.2d;
}

// Along the same line, I've seen this proposed
var cashier = new ICashier {
  // This is more inline with C#'s language, but defeats the purpose of anonymous typing
   decimal CheckOut(Cart c) => cart.Total() * 0.2d;
}

As an alternative, the class should define CheckOut as a delegate; which would respect the C# language constraints. Here is a proposed alternative.

public interface ICashier
{
    // Now lambda expressions can be defined
    Func<decimal, Cart> CheckOut { get; set; }
}

var cashier = new ICashier {
   CheckOut = (c) => cart.Total() * 0.2d;
}
rashadrivera commented 3 years ago

Concerning this new feature's syntax, there have been a few renditions that seem problematic. Below are a few.

public interface IFoo { }
public interface IBar { }

// a common proposed approached would introduce a type of syntax untraditional in C#
var obj1 = new : IFoo { };

// this one proposes mulit-interface implementations; still a bit of an unorthodox syntax
var obj2 = new : IFoo, IBar { };

I propose the approach that would be cleaner and restore balance in the C# language.

public interface IFoo { }
public interface IBar { }

// Interface is declared with the variable
IFoo obj1 = new { };

public interface IFooBar : IFoo, IBar {}
// No need for messy multi-interface definitions when we can cleanly inherit them
// into another interface definition
IFooBar obj2 = new { };
quixoticaxis commented 3 years ago

Was F#-ish syntax considered as extension for anonymous types?

public interface IA
{
    int Call();
}

public interface IB
{
    void Call(int argument);
}

var toCapture = 1;
var o = new { Value = 2 }
    with IA.Call() => Value + toCapture,
    IB.Call(a) => Value + a;
// "var o" resolves to object or to the first mentioned interface
// the nested names hide the scope names

P.s. The feature would need to also enable overrides to re-implement, for example, IEquatable. Given this, it seems reasonable to allow local type definitions as was mentioned above.

CyrusNajmabadi commented 3 years ago

considered as extension for anonymous types?

Anonymous types have not been considered for at least a decade :) About the only thing that has come up for them ever is updating them to support with for anonymous types: https://github.com/dotnet/csharplang/issues/3530

acaly commented 3 years ago

@rashadrivera

With that said, the below code should not be allowed (as mentioned in the Design Details of this issue)

public interface ICashier
{
    decimal CheckOut(Cart cart);
}

var cashier = new ICashier {
  // This is an implicit change to a delegate via the use of a lambda expression
   CheckOut = (c) => cart.Total() * 0.2d;
}

// Along the same line, I've seen this proposed
var cashier = new ICashier {
  // This is more inline with C#'s language, but defeats the purpose of anonymous typing
   decimal CheckOut(Cart c) => cart.Total() * 0.2d;
}

As an alternative, the class should define CheckOut as a delegate; which would respect the C# language constraints. Here is a proposed alternative.

public interface ICashier
{
    // Now lambda expressions can be defined
    Func<decimal, Cart> CheckOut { get; set; }
}

var cashier = new ICashier {
   CheckOut = (c) => cart.Total() * 0.2d;
}

You're converting a method declared in a (anonymous) type into a free function. That is what most dynamic languages (Python, JavaScript, etc.) does. In my opinion, that will make things worse.

Methods and fields are different in that all instances of the same type share the same method, which is immutable, while a field can be modified after creation (unless you define them as init-only, but even then, different instances are not guaranteed to share the same value).

Other similar proposals actually prefer syntaxes similar to normal method declaration in C#, for example, in #738:

public interface ISomething
{
    void Do(int arg);
}

ISomething a = new {
    void Do(int arg) {
    }
};    

If you do want a Func<>, use a struct or class instead of interface.

FANMixco commented 3 years ago

Hi, @acaly that request that you shared https://github.com/dotnet/csharplang/discussions/738 is quite important for proper Android support.

As a Xamarin.Android and Xamarin.Forms developer, I understand the mess of not supporting this. Android relies a lot on these anonymous interfaces and it's not feasible for us to create classes all the time. Many times, when you are binding a library using Visual Studio, it fails epically because of the high dependency of its usage.

Personally, I'm quite surprised why it has been ignored for so long by the Microsoft Team for sure the guys in MAUI have faced similar headaches because of the Android logic itself.

acaly commented 3 years ago

@FANMixco I think most people commenting here would like to see this feature.

I am not sure what is the reason. I hope the comment mentioned above in https://github.com/dotnet/csharplang/issues/4301#issuecomment-754755212 is not. If that is the case, I really hope someone in the team could let us know, otherwise it would be a waste of time for everybody. This similar issue will continue to be raised again and again in the future until something is done (or C# is dead).

HaloFour commented 3 years ago

@acaly

Until someone from the team expresses interest in such a feature this will not go anywhere. If anything a couple members have expressed no interest in exploring anonymous implementations. There seems to be some thought that local classes would be of greater utility.

I had opened a separate discussion specifically to consider Android/Java interop: https://github.com/dotnet/csharplang/discussions/2517

acaly commented 3 years ago

@HaloFour Thanks for the information. I would also love to see the local classes/structs (given it's able to capture local variables) if that is the way the team is going to pursue instead.

leandromoh commented 3 years ago

is there an issue/discussion for local classes/structs ? are they similar to java anonymous classes?

HaloFour commented 3 years ago

@leandromoh

is there an issue/discussion for local classes/structs ? are they similar to java anonymous classes?

Discussion is here: https://github.com/dotnet/csharplang/discussions/130

Zintom commented 2 years ago

Any update regarding this?

I've used the Java equivalent of this feature many times and it is very handy, also; considering Xamarin is now part of .NET as a whole, this would make leaps and bounds helping to interoperate with the Android Libs which all use interfaces as their call-back methods.

CyrusNajmabadi commented 2 years ago

@Zintom There is no update on this.

Zintom commented 2 years ago

There is no update on this.

A true shame 😕

Zintom commented 2 years ago

Excuse my blatant naivety here, but if the compiler can already generate anonymous classes for delegates etc, wouldn't it be relatively simple to expand that to allow the classes to implement an interface, surely all we're looking at here is an additional bit of syntax to indicate the interfaces the delegate will inherit. It doesn't sound like we'd need any change to the runtime either.

Or is it that it's a language feature that the team is not interested in and/or does not see fitting the style of C#?

CyrusNajmabadi commented 2 years ago

@Zintom It's neither of those. First, you are correct that an impl should totally be Feasible. However, you are incorrect that we are not interested or don't see this as fitting. It's just that we haven't seen a suitable proposal that the team believes is a good path forward yet, and many many many other things have our attention as being more appropriate to focus on. :)

Zintom commented 2 years ago

@CyrusNajmabadi Thanks for the response, it's much appreciated.

Do you have any suggestions on how the proposal could be improved to be more pallettable to the team?

P.S, I totally get that there are more important things to work on, however I do think that this would be a very productive feature, especially in terms of Xamarin and the interop with the Android Support Library (Android and the rest of the Java world use anonymous interface classes to facilitate callbacks). I think at the moment the Xamarin team have had to create workarounds for this anywhere a callback is involved.

HaloFour commented 2 years ago

@Zintom

especially in terms of Xamarin and the interop with the Android Support Library

I'm curious as to how many of those interfaces are effectively "functional interfaces" or single-abstract-method? I think a proposal for supporting lambda syntax for declaring implementations of those interfaces would cover a lot of those use cases and would be fairly simple to describe and implement without requiring new syntax.

CyrusNajmabadi commented 2 years ago

especially in terms of Xamarin

In a case like this, having Xamarin requesting, with clear arguments as to how it helps, and why it's the best solution going forward, would help.

CharlieDigital commented 1 year ago

I have a vote for another use case: message contracts.

It would be great if we could just define the "shape" of the message via an interface and have an anonymous object be able to fulfill that shape especially given that in many of these cases, the object will simply be serialized and deserialized on the other end.

FANMixco commented 1 year ago

I'm wondering if we will ever see it happen!

Will-at-FreedomDev commented 1 year ago

I think a fantastic use-case for this would be to allow ASP.NET Core controllers to bind to interfaces and behind the scenes it could use an anonymous objects. In order to achieve something like this now, we would need to also implement concrete CreateEntity and UpdateEntity classes which also implement these interfaces to get the same/similar result.


interface ICreateEntity 
{
    string Name { get; set; }
}

interface IUpdateEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Entity: ICreateEntity, IUpdateEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Controller 
{
    Entity Get(int id) 
    {
       return new Entity { Id = id }; 
    }

    void Post([FromBody] ICreateEntity entity) { ... }

    void Put([FromBody] IUpdateEntity entity) { ... }
}
HazyFish commented 11 months ago

This is another use case: https://github.com/dotnet/csharplang/discussions/7719

hez2010 commented 8 months ago

I think if we choose to add it to C#, it should support all kinds of types that can be casted or boxed into interfaces:

public interface IClient
{
    string Name { get; }
}

public interface ICustomer
{
    string AccountNumber { get; }
}

var client = new class : IClient, ICustomer {
    public string Name => "Khalid",
    public string AccountNumber => "8675309"
};

var client = new struct : IClient, ICustomer {
    public string Name = "Khalid",
    public string AccountNumber = "8675309"
};

var client = new record class : IClient, ICustomer {
    public string Name => "Khalid",
    public string AccountNumber => "8675309"
};

var client = new record struct : IClient, ICustomer {
    public string Name => "Khalid",
    public string AccountNumber => "8675309"
};
CharlieDigital commented 8 months ago

@hez2010

It feels like it should never have private members at all.

public interface IClient
{
    string Name { get; }
}

public interface ICustomer
{
    string AccountNumber { get; }
}

var client = new class : IClient, ICustomer {
    Name => "Khalid",
    AccountNumber => "8675309"
};

var client = new struct : IClient, ICustomer {
    Name = "Khalid",
    AccountNumber = "8675309"
};

var client = new record class : IClient, ICustomer {
    Name => "Khalid",
    AccountNumber => "8675309"
};

var client = new record struct : IClient, ICustomer {
    Name => "Khalid",
    AccountNumber => "8675309"
};

Feels cleaner? But overall think it's a nice proposal.

Has the benefit that if you wanted to attach methods, it would still be possible via extension methods on IClient or ICustomer.

FANMixco commented 7 months ago

I feel that this will never be implemented.

MaxGuernseyIII commented 5 months ago

@FANMixco ...but it would be great if it was.

toupswork commented 3 months ago

I have been a C# programmer since .NET Framework 1.1

I have been wishing, hoping, wanting, praying for this feature more than anything else.

For God's sake, you guys made a concession and put in default parameter values that everyone now abuses to hell.

You put in dynamic types as a concession!

You implemented closures with hidden classes!

You use duck typing inconsistently.

Yet you won't give us this? Something that I could use so much. It would make it easier to create simple test mocks.

I .. don't... get... it...!!!

CyrusNajmabadi commented 3 months ago

@toupswork No one on the LDM think this is beneficial enough to to in the near term. That may change if the landscape of the ecosystem changes. But currently this isn't rising to that level for any of us, or our partner teams.