dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
18.92k stars 4.02k forks source link

[Proposal] add supersede modifier to enable more tool generated code scenarios. #5292

Closed mattwar closed 7 years ago

mattwar commented 9 years ago

Some C# language features exist primarily for tooling scenarios. Designers, build time tools and other code generators produce source code that compiles together with and augments user written code. Partial classes and partial method are two of these.

With partial classes, a tool can generate source files that make additional declarations for a class, adding members, interfaces and attributes on top of what the user wrote.

Partial methods allow tools to declare signatures for methods that don’t actually exist, but that can be supplied elsewhere by the user. In this way the tool is free to generate code invoking this partial method without requiring the method to have been fully supplied. They are limited to private void returning methods so that they can vanish during compilation if the user has not supplied a full declaration with a body for the partial method.

In this way, partial methods are a clever trick enabling tool generated code to interact with user written code in more than just the simple additive way that partial classes provide. However, it is a very narrow trick. It allows the user to enhance the behavior of the tool generated code at well-defined spots by providing method implementations, but it does not allow the tool generated code to enhance the behavior of the user written code.

For example, imagine I wanted a tool to generate code that adds entry and exit logging to my user written code (without actually changing my source files or post processing the binary.) I could pervert the use of partial methods to approximate this, by inverting how they are normally used. I could pre-establish an agreement between my code and the tool (probably established by the tool), and liberally sprinkle invocations to logging methods throughout my code base, and then let the tool generate the bodies.

// user written code
public partial class MyClass
{
    private partial void log_entry(string name);
    private partial void log_exit(string name);

     public void Method1() 
     {
          log_entry(“Method1”); 
          // something totally awesome here
          log_exit(“Method1”);
     }
}

// tool generated code
public partial class MyClass
{
    private partial void log_entry(string name)
    {
         Console.WriteLine(“entered {0}”, name);
    }
    private partial void log_exit(string name);
    {
        Console.WriteLIne(“exited {0}”, name);
    }
}

Then I could choose to run the tool or not, so that the calls to log_entry and log_exit either had an implementation at runtime or not.

But I would have to manually place calls to these methods everywhere throughout my code. And I’d have to do this over and over again for each additional tool that needed to interact with my code. A very tedious prospect, and one better suited for tools generating code than my tired fingers.

A better solution would be one that allowed the generated code to enhance the user written code directly, by replacing it or overriding it, and placing all the burden of declaration into the tool generated code.

Proposal: Superseding members

The supersedes feature enables just this. It allows class members to override members defined in the same class, by superseding their declaration with a new declaration using the supersede declaration modifier.

// user written code
public partial class MyClass
{
      public void Method1()
      {
          // something totally awesome here
      }
}

// tool generated code
public partial class MyClass
{
      public supersede void Method1()
      {
           Console.WriteLine(“entered Method1”);
           superseded();
           Consolel.WriteLine(“exited Method1”);
      }
}

In this scenario the tool generated Method1 supersedes the user written Method1. All invocations are directed to the tool generated method. The tool generated implementation can then invoke the user written method using the superseded keyword that points at the member that was superseded by this declaration.

Using this technique, I could have a tool generate my boilerplate INotifyPropertyChanged implementations.

// user written code
public partial class MyClass
{
     public int Property { get; set; }
}

// tool generated code
public partial class MyClass  : INotifyPropertyChanged
{
     public event PropertyChangedEventHandler PropertyChanged;

     public supersede int Property
     {
          get { return superseded; }
          set
          {
              if (superseded != value)
              {
                    superseded = value;
                    var ev = this.PropertyChanged;
                    if (ev != null) ev(this, new PropertyChangedEventArgs(“Property”));
              }
          }
     } 
}

Multiple Generators

Of course, since the primary scenario for this feature is tool generated code and there is likely going to be times where there is more than one tool wanting to supersede a user written member, some mechanism should exist that allows one bit of tool generated code to supersede another bit of tool generated code without requiring them to know about each other.

Proposal: superseding superseded members

We can allow more than one superseded member declaration to exist at the same time. One of the superseding members directly supersedes the original member. Another superseding member then supersedes the prior superseding member, and so on. Thus a chain of superseding members exists similar to how multiple overrides might exist on the same member in an inheritance hierarchy. In fact, it is somewhat helpful to think of superseding as directly comparable to overriding. If you imagine a series of overrides of a member in a vertical inheritance hierarchy, you could image superseding as kind of a horizontal overriding (between partial class declarations).

// user written code
public partial class MyClass
{
      public void Method1()
      {
          // something totally awesome here
      }
}

// tool #1 generated code
public partial class MyClass
{
      public supersede void Method1()
      {
           // do something interesting
           superseded();
      }
}

// tool #2 generated code
public partial class MyClass
{
      public supersede void Method1()
      {
           // do something else interesting
           superseded();
      }
}

Order of supersession

The only problem we have is determine which superseding member is the first, the next and so on. The order is going to matter, because it determines which gets called first and that can affect behavior of the entire composition.

Unlike member overrides that occur in derived classes that specifically call out the names of the classes they are deriving from, the supersede syntax doesn’t directly identify the precise member that is being superseded. And that’s a good thing, because if it did, then code generating tools would absolutely need to know about each other, so they could reference each other’s declarations.

Instead, we need a different technique (other than naming) to identify ordering between the superseded members.

Proposal: lexical order

We can use lexical order to determine the order in which members are superseded. (For multiple source files, consider the files concatenated in the order supplied to the compiler.)

  1. The member without the supersede modifier is the base implementation.
  2. The superseding member that is earliest in lexical order is the first to supersede the base.
  3. The superseding member that is next in lexical order supersedes the previous superseding member, and so on.

In this way, no additional syntax needs to be invented to support having multiple superseding members for a single base member. The natural order of their declaration determine the order for superseding.

(This does not consider how the generated source files are given their order.)

gafter commented 9 years ago

AOP FTW!

TonyValenti commented 9 years ago

Great proposal!

Are there any other options for changing the way the original member is invoked? I like the new "superseded" concept and I think it should feel similar to the way that "mybase" operates. Perhaps it should be called like extended.Member() ? This would also allow other members to potentially differentiate if they want the new declaration or, perhaps, want the original.

// user written code
public partial class MyClass
{
     public int Property { get; set; }
}

// tool generated code
public partial class MyClass  : INotifyPropertyChanged
{
     public event PropertyChangedEventHandler PropertyChanged;

     public extended int Property
     {
          get { return extended.Property; }
          set
          {
              if (extended.Property != value)
              {
                    extended.Property = value;
                    var ev = this.PropertyChanged;
                    if (ev != null) ev(this, new PropertyChangedEventArgs(“Property”));
              }
          }
     } 
}
paulomorgado commented 9 years ago

@mattwar, besides allowing multiple supreseeded implementations of the method, what else can you do with this that you can't with the current implementation?

MgSam commented 9 years ago

This is interesting in the context of enabling AOP without post-compilation rewriting (a la PostSharp). However, I don't actually see any part of the proposal that specifies how you would define an aspect or apply it to code. You definitely would not, for example, want to be manually invoking some tool in Visual Studio every time you want a class to implement INotifyPropertyChanged.

Besides being a poor workflow, it would leave your source with no indication what magical properties are being added to it elsewhere. I think you absolutely need to have either new keyword(s) to express these behaviors, or use Attributes.

Lastly, the proposal for how you order superceeded items seems like a total non-starter to me. Having the build order determine semantic meaning of code is dangerous and completely non-obvious to anyone reading the code. The ordering would need to be part of the aforementioned aspect keyword/attributes. This is what PostSharp does.

HaloFour commented 9 years ago

Not what I envisioned for AOP at all. I expected something more like an analyzer/code-fix where the processor would be handed the syntax tree, transform it and then return it to the compiler during the compilation process (although being able to run it separately and view the generated source representing the syntax tree would be very useful for debugging scenarios.) Would a processor in this case be expected to parse source or trees or either?

I am also curious as to when whatever generates this source would be used. Having worked with T4 it's not the most pleasant experience in the world (particularly paired with an SCM plugin, and I'm looking at you Entity Framework). Even if source were to be spit out of such a processor I'd want it easily integrated into the compilation process.

I do think that there are scenarios where order of "supercession" would be important. A processor adding validation logic may want to bail ahead of a processor adding transaction logic or similar.

mattwar commented 9 years ago

@TonyValenti I considered having superseded refer to the instance (like base does), but it turns out the scenario is quite different. With base, the whole class is overridden, so its possible to use it to reference any member in the base class. Yet with supersede, each member is superseded individually. The class itself is never superseded. All parts of the partials are the same class. So it only made sense to have it reference the other member directly.

mattwar commented 9 years ago

@paulomorgado what are you referring to as the current implementation? Do you mean the partial methods I used as an example, or just any/all existing language features?

mattwar commented 9 years ago

@MgSam, @HaloFour This proposal is separate from any discussion of actual implementation of code generating tools or how they would interact with the compiler. This feature is meant strictly as a way to enable such generators should they exist with a means to add behavior to code already written without resorting to IL rewrites, etc.

mattwar commented 9 years ago

@HaloFour it would be important for the code generators to be able to order their generated declarations.

MgSam commented 9 years ago

@mattwar That makes sense, but I don't think its possible to get a complete picture on the merits of this proposal without a complimentary proposal for how you would create and manage aspects. Very similar to how primary constructors/records/tuples/pattern matching need to all be considered together and how they'll interact rather than trying to only build each piece in isolation.

mattwar commented 9 years ago

@MgSam I agree, it would be better to consider it with a full code generation proposal. Maybe I should write one.

bbarry commented 9 years ago

I'm not opposed to the keyword pairs supercede / superseded but are others being evaluated?

Perhaps aspect / inner or outer / inner?

As to ordering, assuming the generation tools could provide a sequence to filenames lexical order seems good enough. The built in AOP tools could process a file like this:

Community tools for other processes (some custom t4 processor?) could create things like MyClass.timings.cs and since "t" comes after "g" it would come after it in order in terms of processing the partial class declaration. The built in tool may derive order from some predefined mechanisms but it will presumably generate these aspect classes in a particular order and can then decide to name them so that they sort in a defined manner.

Should there also be some strict compilation rules around matching signatures?

Should this compile:

public partial class MyClass
{
  public void Method1(int a, int b)
  {
      // something totally awesome here
  }
}

public partial class MyClass
{
  public supersede void Method1(int b, int a)
  {
       superseded(b, a);
  }
}

public partial class MyClass
{
  public supersede void Method1(int a, int c)
  {
       superseded(c, a);
  }
}
HaloFour commented 9 years ago

@bbarry

Perhaps intercept and intercepted?

The file name issue would fall under the lexical ordering as stated above. To my knowledge the C# compiler doesn't currently care what order those files are provided, and the file name itself is irrelevant. Going by the order in which the files are passed to the compiler could at least be deterministic. Basing the order by file name, particularly using a natural sort, could cause issues in different locales/languages.

I would imagine that the same rules would apply to supercession as applies to partial methods in that the signature must match exactly.

tpetrina commented 9 years ago

Can we rather use typesafe macro expansions? The proposed syntax looks rather...unwieldy.

paulomorgado commented 9 years ago

@mattwar,

current == C#6

Other than allowing havoc with multiple generators running over each other on a not yet defined or possibly never deterministic way, what can you do given this proposal is implemented that you can't now (C#6)?

mattwar commented 9 years ago

@paulomorgado It is not what you can do with it that you could not do before, it is what tool generated code can do for you that it could not do before without post build rewriting. For example, a tool can generate all the boilderplate code you need to implement INotifyPropertyChanged for classes you declare using normal C# syntax. You and the tool get to collaborate on the content of classes and members. The existing partial classes feature only gets you part way there.

mattwar commented 8 years ago

A review of this proposal occurred a while back (in 2015) that included some brainstorming of alternate keywords to use for supersedes and superseded.

Instead of supersede/superseded use replace/original.

// user written code
public partial class MyClass
{
      public void Method1()
      {
          // something totally awesome here
      }
}

// tool generated code
public partial class MyClass
{
      public replace void Method1()
      {
           Console.WriteLine(“entered Method1”);
           original();
           Consolel.WriteLine(“exited Method1”);
      }
}

This would get rid of some of the awkwardness of everyone disagreeing on how to spell the keywords.

vbcodec commented 8 years ago

The original() is good and ismple, but how we call original other method (for example Methos2) from replaced Method1, if Method2 also was defined in replacement class ? Maybe there should be additional kewyord called original, so we could write:

  public replace void Method1()
  {
       Console.WriteLine(“entered Method1”);
       original(); // or original.Methos1();
       original.Methos2();
       Consolel.WriteLine(“exited Method1”);
  }

  public replace void Method2()
  {
    ...
  }

What about to use replacement to classes from libraries ? This should be possible at least to app code - not altering original methods in libraries, but use wrappers.available only to app code.

alrz commented 8 years ago

Would it be possible to alter compiler generated code for records?

sealed class Person(int Id, string Name);
partial class Person {
  replace bool Equals(object other) {
    return (other as Person)?.Id == this.Id;
  }
}

If calling the original is mandatory, I strongly suggest to use decorate instead of replace. And if it's not, perhaps only one of replaced members is allowed to NOT call original?

paulomorgado commented 8 years ago

I'm still :-1: on this proposal.

If the developer of the extended class intended for it to be extended, it should have done it in a well thought and documented way.

alrz commented 8 years ago

If the developer of the extended class intended for it to be extended, it should have done it in a well thought and documented way.

@paulomorgado I don't get it, you mean if the class has partial declarations it should be documented? Sure, but this doesn't go beyond assembly boundary so there is no "extended" classes, just partial classes which we had before.

paulomorgado commented 8 years ago

"extended" in the sense that a partial definition (not a partial class) is added to the generated definition.

Isn't this whole proposal about partial class definitions and a way to override/supersede a default definition?

alrz commented 8 years ago

"extended" in the sense that a partial definition (not a partial class) is added to the generated definition.

@paulomorgado replace is only useful when you have a partial class and it's up to you to use #5561 to add a partial declaration or not. Actually I thought that original is mandatory in a replace declaration and proposed #9178 but it seems that it's not (accourding to #9532).

Isn't this whole proposal about partial class definitions and a way to override/supersede a default definition?

It's actually about decorating existing members so that one can add additional logic to a method/property. I'm not sure that I understand your objection though.

aL3891 commented 8 years ago

Would really love to see this, it would enable a whole bunch of cool stuff!

rn-3 commented 8 years ago

This would definitely enable a lot of cool stuff that currently requires external tools like PostSharp, I would really love to see this come out.

How about introducing a mechanism to protect the members of a partial class from being 'replace'-ed? I think that would be a useful scenario for library developers where the library is open source distributed for the clients to customize & extend. This mechanism protects and guarantees that certain pieces of the partial class cannot be replaced/superseded in a new file for this partial class...

For example use a new keyword like nonreplaceable

// File-1

nonreplaceable void NonReplaceableMethod()
{
    // some logic goes here...
    Method2();
}

void Method2() { }

// File-2

replace void Method2()
{ /* custom logic here */ }

replace void NonReplaceableMethod() **// this will generate compiler error**
{ /* custom logic here */ }
vladd commented 8 years ago

@rn-3 What are the scenarios where your proposal can be useful?

If the library users have the source code of the library, they don't need to supersede, they can just edit the method in question and recompile the code. And if they don't have the source code (and don't bother to decompile the library, or the assembly in question is signed), they cannot add class parts: supersede doesn't (and cannot possibly) work across assemblies.

rn-3 commented 8 years ago

The library users will have source code but editing the original files would not be recommended since that would break pulling in future updates to the library. The partial classes would be used to customize or extend the library by adding new files and not modifying original source files. Class parts do not work across assemblies – that’s the reason for distributing the library along with the source files & projects...

From: vladd Sent: Thursday, April 14, 2016 3:39 PM To: dotnet/roslyn Cc: rn-3 Subject: Re: [dotnet/roslyn] [Proposal] add supersede modifier to enable more tool generated code scenarios. (#5292)

@rn-3 What are the scenarios where your proposal can be useful?

If the library users have the source code of the library, they don't need to supersede, they can just edit the method in question and recompile the code. And if they don't have the source code (and don't bother to decompile the library, or the assembly in question is signed), they cannot add class parts: supersede doesn't (and cannot possibly) work across assemblies.

— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub

axel-habermaier commented 8 years ago

@mattwar: According to the latest spec (?) of this feature, it seems that extern methods cannot be replaced. Any reason why? It seems that at the moment, extern is only used for internal CLR calls as well as [DllImport] scenarios; with code generation, extern could finally become more broadly applicable for all situations in which you don't care about the original method, but only the method's signature is important for code generation scenarios. It would save you from writing meaningless empty bodies if the original is to be fully replaced anyway.

For instance, to kick off code generation for some method signature T M(int f, out S s), instead of the verbose declaration

public T M(int f, out S s) 
{
      s = default(S);
      return default(T);
}

you could just write the slightly shorter

public extern T M(int f, out S s);

Admittedly, it's a minor benefit, but what's the reason for explicitly not allowing it? In fact, that use case is the very definition of why extern was added to the language in the first place: The body of the method is defined elsewhere - whether it is runtime support as for [DllImport], CLR internal calls, or code generation should be irrelevant, in my opinion. I do agree with not being able to replace abstract methods.

axel-habermaier commented 8 years ago

@mattwar: Also, another question: Am I allowed to replace getter-only properties? With what? Would the following work? Which Y is assigned in the constructor?

partial class X 
{
      public X() 
      {
            if (someCondition) 
                 Y = 99;
      }

      public int Y { get; } = 33;
}

partial class X
{
     public replace int Y => someCondition ? 2 : original;
}
mattwar commented 8 years ago

@axel-habermaier about extern, that is certainly something that could be covered by this proposal though its not explicitly mentioned. I've had others mention it as well.

As for your example, yes you could replace the getter-only property just as you've written.

alrz commented 8 years ago

@mattwar What is the reason to require these to match?

First two just make replacement merely more verbose. And the last one prevents to add a setter to a property in replaced member.

mattwar commented 8 years ago

@alrz I don't recall making any detailed claim that would rule any of those out.

axel-habermaier commented 8 years ago

@mattwar: It's in the spec:

The following must match when replacing a member: 1. Signature: name, accessibility, arity, return type, parameter number, parameter types and ref-ness 1. Parameter names and default values (to prevent changing the interpretation of call-sites) 1. Type parameters and constraints 1. Attributes on the member, parameters, and return type (including this for extension methods) 1. Set of accessors in properties and events 1. Explicit implementation of the member 1. Modifiers: sealed, static, virtual, new, and override.

Also, why the following restriction?

The default constructor can be added by a generator but not replaced.

vladd commented 8 years ago

@axel-habermaier Hm, how would you implement constructor replacing? If you are going to convert the replaced constructor into a plain method, you won't be able assign to readonly fields in it. If you are going to convert the replaced constructor into a chained constructor, your only option is to call it at the beginning (and you'll need to invent a fake signature as well, you can't just rename it -- but this seems to be a lesser problem).

vladd commented 8 years ago

@alrz I would assume the reason is the principle of least surprise. If I see a getter-only protected property, and see some attributes on it, I don't expect it to be in fact a public getter/setter property. From my point of view, the mental model of the replaced properties (as well as methods etc.) should be "adding some minor tweaks/boilerplate on the existing implementation", not more.

axel-habermaier commented 8 years ago

@vladd: The spec specifically allows constructor replacement, it only doesn't allow default constructor replacement. Wait, does "default constructor" always refer to the zero-parameter constructor generated by the compiler if there are no other constructors? In that case, it makes sense that it cannot be replaced, as there is nothing to replace in the first place.

alrz commented 8 years ago

@vladd

how would you implement constructor replacing? If you are going to convert the replaced constructor into a plain method, you won't be able assign to readonly fields in it.

It wouldn't be a separate method, replace is solely a compile-time transform so when you replace a constructor, it literally replaces the constructor. You can't do this with existing types in other assemblies because you need to mark the type as partial.

I would assume the reason is the principle of least surprise

replace essentially causes surprises because you can replace the whole method body with whatever you want without any further argumentation, unless you required an attribute on them which is totally up to you. So adding a setter to a getter-only property wouldn't be much of a surprise, however, I agree that it shouldn't be able to remove accessors, but I don't see any problem if we could add accessors in the replaced property. The thing is that there are chances that you are aware of generators in your project. So you shouldn't be surprised by any of these in the first place.

If I see a getter-only protected property, and see some attributes on it, I don't expect it to be in fact a public getter/setter property.

I didn't say it should be able to change the access modifier, I said it should be able to be absent in the replaced method, so when you have a public/protected or whatever member, you can replace it with a matching member without any access modifier on it. This is what we currently have in partial classes, access modifiers must be the same in all parts or can be just absent. However, it should be possible to specify the access modifier in the replaced member when it's not specified in the source. In other words they should all be agreeing with each other, not conflicting, regardless of the member being a replace or otherwise.

@axel-habermaier

does "default constructor" always refer to the zero-parameter constructor generated by the compiler if there are no other constructors? In that case, it makes sense that it cannot be replaced, as there is nothing to replace in the first place.

If the source type does not have a default constructor you can simply add it in a partial type without replace modifier on it. If the source type does have a default constructor then it has a body and it is something to replace.

partial class C1 {}
partial class C1 { public C1() { ... } }

[A] public partial class C2 : C3 { [A] public C2() : base(...) { ... } }
// absent/additive/matching ctor initializer, class base, access modifiers
[B] partial class C2 : IA { [B] replace C2() { /* probably calls `original` */ } }

I think for constructors we should also be able to either omit the constructor initializer in the replaced member, or it should match with the original and if the source constructor does not have any constructor initializer you should be able to add it in the replaced constructor. Just like class-base in partial types.

As for attributes it's a little more complicated. The question is that you want to replace attributes as well or you just want to merge them all? Note that partial methods do merge attributes (§10.2.7):

  • The attributes in the resulting method declaration are the combined attributes of the defining and the implementing partial method declaration in unspecified order. Duplicates are not removed.
  • The attributes on the parameters of the resulting method declaration are the combined attributes of the corresponding parameters of the defining and the implementing partial method declaration in unspecified order. Duplicates are not removed.

I think it should be clear that what you want to do in these scenarios because both option might be required depending on the use case. But if I hadn't a choice, I would definitely go with combination.

mattwar commented 8 years ago

@axel-habermaier That's Chuck's spec corresponding to what he's implemented for first round so we can get feedback. It's not a final definition of the feature.

vladd commented 8 years ago

@alrz

It wouldn't be a separate method, replace is solely a compile-time transform so when you replace a constructor, it literally replaces the constructor.

Well, are we discussing the proposition as it's stated at the top of this issue? The current proposition doesn't transform methods at compile time, it just renames the existing methods and adds a new method which has [a possibility] to call the original one. As far I understand, this is why partial is required, because the tools are going to produce new source files rather than modify the compilation process. And as far as I understand, this is how the user would be able to debug the superseded code: all the instances of method will be available in the source.

So for the methods there is no magic going to take place: just one plain old method calls another one.

I expect that for the constructors there will be the same technique used (why otherwise?), so the question remains.

I said it should be able to be absent in the replaced method, so when you have a public/protected or whatever member, you can replace it with a matching member without any access modifier on it.

Completely agree here.

So adding a setter to a getter-only property wouldn't be much of a surprise

... but this is something I don't agree. If I have in my code a getter-only property, I personally would be very surprised if assignment to the said property compiled. (Moreover, adding a setter to a property changes the meaning of assignment in constructor!) And by the way, adding a dummy setter is not too much of a burden for the developers. My point is that one shouldn't be required to look through all the generated files in order to find out whether the property has a setter.

alrz commented 8 years ago

@vladd

Moreover, adding a setter to a property changes the meaning of assignment in constructor

Replacing a read-only auto-property with anything other than a read-only auto-property changes the meaning of assignment in the constructor, i.e. a compile-time error.

// perfectly fine
partial class C { int P { get; } C() { P = 1; } }
// results in a compile-time error at `P = 1;` in the resultant type
partial class C { replace int P { get { return 0; } } }

So how is that any better? You must be able to add a setter in this case. :)

The current proposition doesn't transform methods at compile time, it just rename the existing methods and adds a new method which has [a possibility] to call the original one.

If you say so, "calling the original member" would make sense. But I think original(...); shouldn't be callable that way, because you can dramatically change the call site by passing any possible value to the original method parameters which is against of your argument regarding principle of least surprise. I don't think that is a good idea, replace should be able to only change the body of the original and nothing else. In that case, original; (without an argument list) would be sufficient, hinting the compiler to call the original with exact same arguments passed to the method.

I expect that for the constructors there will be the same technique used (why otherwise?)

Because a constructor ain't no method, so it can't be renamed, it's able to assign readonly fields. etc. In this case original; would mean to insert source constructor's body at its position.If you think that way, you can see why absence of the constructor initializer in the replaced member can be a better option rather than duplicating it.

vbcodec commented 8 years ago

Does this feature handle 'replacing' functions / properties from libraries, or only from sources ?

aL3891 commented 8 years ago

@vbcodec only sources and only within the same project. The primary use case (as i see it) is for code generation, like for replacing an auto property with a more complicated implementation or generating code versions of views in an asp.net app

bbarry commented 8 years ago

It would be really nice if I could change the return type of a method in a replacement.

Toying around with #261, I feel like I could write this:

public static ValueTaskEnumerable<int, Af.Enumerator> AfYieldingStuff()
{
  Af.Await(ValueTask_Foo());
  Af.Yield(1);
  Af.Await(ValueTask_Bar());
  Af.Yield(2);
  return Af.Break<ValueTaskEnumerable<int, Af.Enumerator>>();
}

and use code generators to generate a zero allocation async foreach method YieldingStuff. It would be nice though If I could instead write:

[ReplaceThis]
public static async ValueTaskEnumerable<int> YieldingStuff()
{
  await ValueTask_Foo();
  yield return 1;
  await ValueTask_Bar();
  yield return 2;
}

and generate this:

public static replace ValueTaskEnumerable<int, Cg3> YieldingStuff()
{
  var machine = new Cg3();
  ...
}

public struct Cg3 : IAsyncStateMachine  { ... }
alrz commented 8 years ago

@bbarry That is a valid request but also very dangerous. If replace can change the return type, it could easily go wrong. what if you didn't mean to change it? I think replaceable parts should be categorized to wholly (replace) and partially (partial, #9178) then you can use whichever fits your use case.

bbarry commented 8 years ago

I think it is potentially dangerous, but I am not sure it matters. You would need to do some explicit action to run a generator (import a nuget or write a generator in your code somewhere) so as the author of a project, you have full control over what generators are being run. Surely you have made an intention to change this then right?

I suppose it doesn't really matter, I could just as easily write:

[StructEnumerableCodeGen("public static", "YieldingStuff")]
private async IAsyncEnumerable<int> YieldingStuffCodeGenOriginal()
{
  await ValueTask_Foo();
  yield return 1;
  await ValueTask_Bar();
  yield return 2;
}
IanRingrose commented 8 years ago

I would like a clear way to see that a method is being superseded.

For example.

// user written code
public partial class MyClass
{
      public supersedeable void Method1()
      {
          // something totally awesome here
      }
}

So that anyone reading the code from Method1() knows something special is going on. Please come up with a better keyword then supersedeable for it!

gafter commented 8 years ago

@IanRingrose Whenever a new feature is added to the language, some people ask for a really explicit syntax so it is obvious that "something special" is going on. But once the language feature has been in the language for a few months, the "something" is no longer "something special" and that extra syntax just becomes bothersome boilerplate.

axel-habermaier commented 8 years ago

@IanRingrose: Also, this is primarily a tooling issue. Visual Studio could simply show an icon that indicates that the method is replaced, potentially even allowing you to jump to the replacement method with a click/shortcut.

biqas commented 8 years ago

@axel-habermaier agree that instead of expressing it in the language it is more a tooling issue like described here #11038.

IanRingrose commented 8 years ago

A partial method, has the partial keyword used on both sides, so you can see the empty method may be replaced. (Likewise virtual methods are marked in base classes, so you can see that a sub class may redefine them.)

Therefore I think it is reasonable that a method that will be superseded should likewise have a keyword on both sides, so you can see that it may be superseded.