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

Extension operators #4945

Closed
dtsiniavskyi opened this issue Sep 2, 2015 · 19 comments
Closed

Extension operators #4945

dtsiniavskyi opened this issue Sep 2, 2015 · 19 comments
Assignees

Comments

@dtsiniavskyi
Copy link

Feature request, Language C#

I would love to see more extension like stuff in C#. In this case - extension operators. It would be great to have ability to implement operators outside theirs' class or in other projects. Something like:

public static class Extensions
{
    public static explicit operator ViewModel(Model b)
    {
        return new ViewModel { Foo = b.Foo.ToString() };
    }
}

so var viewModel = (ViewModel)model; code would be compilable like explicit operator was implemented inside ViewModel class.

@orthoxerox
Copy link
Contributor

+1 to that. string doesn't implement IReadOnlyList<char>, but an extension implicit cast would solve this issue for my purposes.

@dsaf
Copy link

dsaf commented Sep 2, 2015

@orthoxerox I would raise an issue in https://github.com/dotnet/corefx/issues as well.

@orthoxerox
Copy link
Contributor

@dsaf, it's already there in coreclr, since System.String is in mscorlib: https://github.com/dotnet/coreclr/issues/542

@aluanhaddad
Copy link

I very much like the idea of being able to write extension operators. This would be particularly useful for writing DSLs.

I think you should expand this proposal to include additional scenarios that demonstrate it's usefulness as the type casting example is not particularly strong. In particular, a generic extension operator.

I have one gripe with your proposed syntax. Specifically, I think that the this keyboard should be included and required for the sake of both consistency with existing extension forms and because it makes it clear that a type is being extended.

@rftobler
Copy link

@aluanhaddad A few day ago I posted an example of using an extension operator for specializing generics that uses the this keyword: #5165. I think this example shows more of the power of operators as extension methods.

@alrz
Copy link
Member

alrz commented Apr 15, 2016

This would allow to use types themselves as patterns which cause a conversion, e.g.

static class Extensions {
  public static explicit operator int?(string value) {
    return int.TryParse(value, out var result) ? result : null;
  }
}

// assuming a non-static `bool operator is(string, out string[])` for `Regex`
Regex regex = new Regex("...");
if(str is regex({int value})) {
  // ...
}

// translates to
if(regex.op_Match(str, out string[] captures)) {
  if(captures.Length == 1) {
    var temp = Extensions.op_Explicit(captures[0]);
    if (temp.HasValue) {
      var value = temp.GetValueOrDefault();
      // ...
    }
  }
}

Related: #10598, #5811

@gafter
Copy link
Member

gafter commented Apr 15, 2016

@alrz

This would allow to use types themselves as patterns which cause a conversion, e.g.

No, it would not. Pattern-matching does not apply user-defined conversions. If it did, it would use the implicit ones only. Also, there is no "array pattern".

@alrz
Copy link
Member

alrz commented Apr 15, 2016

@gafter I assumed the "array pattern" part. Anyway, so if we define it as implicit it does apply the conversion right? I'm asking because #10429 states that this would work only for constants.

@gafter
Copy link
Member

gafter commented Apr 15, 2016

@alrz The only implicit user-defined conversion that would apply in any pattern-matching-like context is the conversion from the switch-expression of a switch-statement to a type that was switchable in C# 6: an enum, integral type, string, etc. We do not apply user-defined conversions in any other pattern-matching context.

@alrz
Copy link
Member

alrz commented Apr 15, 2016

@gafter Is there a specific reason for this? Wouldn't it be more consistent if "it was allowed for all the types that is switchable in C#?". This statement is still true for C# 6.0 -- it applies the conversion to all the possible types for switch. Now that those types are relaxed to almost all types, why still stick to those restricted set of types (or none at all, in other contexts) when it comes to implicit conversion?

@gafter
Copy link
Member

gafter commented Apr 15, 2016

@alrz Those are the types that can have constant expressions (except float, double, and decimal).

@alrz
Copy link
Member

alrz commented Apr 16, 2016

@gafter Ok, if C# had support for extension is operators (I hope) that wouldn't be much of an issue,

static class StringExtensions {
  public static bool operator is(this string str, out int value) =>
    int.TryParse(str, out value);
}

if(str is regex({ string(int value) })) 
// and if we could omit known types in positional patterns
if(str is regex({ (int value) }))

if(regex.op_Match(str, out string[] captures)) {
  if(captures.Length == 1) {
    if(StringExtensions.op_Match(captures[0], out int value)) {
      // ...
    }
  }
}

That would be just perfect.

@gafter
Copy link
Member

gafter commented Apr 16, 2016

Yes, obviously pattern-matching on something of type string should parse it as an int. </sarcasm>

@alrz
Copy link
Member

alrz commented Apr 16, 2016

@gafter I am sure you saw my point though. I've defined an is operator for the type string that has an out parameter of type int so that you could bind it in a pattern to match against a string.

Basically T(..) pattern is only valid if the type T has an applicable is operator overload, right? So if we define something like bool is(string, out int) it would become a fallible positional pattern for the type string that could be bound to a variable of type int (or any other pattern that is compatible with the said type, in this case, int, e.g. a constant pattern) regardless of the implementation. Yes the example above does parse the input, I don't know what is wrong with that. I'm just saying that an extension operator is would enable us to do these kinds of utterly unreasonable evil dark magic. </sarcasm>

@gafter
Copy link
Member

gafter commented Apr 16, 2016

@alrz I think you're imagining that pattern-matching does overload resolution on all of the possible operator is methods that it can find, based on the "expected" types of the patterns. It doesn't do that. If this is the pattern-matching operator for string, then parsing a string as an integer is what pattern-matching on a string (with one subpattern) would mean.

@alrz
Copy link
Member

alrz commented Apr 16, 2016

@gafter So is overloads must have different number of out parameters regardless of their types? That is understandable because something like string(var value) would be ambiguous if it was overloaded. However, that would make some other use cases impossible to implement. What if I want to define two active patterns for string type like bool is(string, out int) for parsing case, and void is(string, out char[]) for extracting the char array? It would be nice if in these cases compiler require the explicit type (i.e. a type pattern) instead of var because the type obviously is not known to be omitted (something akin to "type ascription" to hint the compiler that what is the expected type in this particular case). This would be applied to out var as well; if you have multiple overloads which only differ in types then you cannot use out var and you must explicitly mention the expected type to select the desired overload.

@gafter
Copy link
Member

gafter commented Apr 16, 2016

@alrz

What if I want to define two active patterns for string type like bool is(string, out int) for parsing case, and void is(string, out char[]) for extracting the char array?

You write a class that names each pattern you're trying to define, and you place the pattern-matching operation there. And then you use that name in the pattern so it is clear to the reader which of them is being used.

@alrz
Copy link
Member

alrz commented Apr 17, 2016

@gafter So that would be something like this:

class Integer {
  public static bool operator is(string str, out int value) => int.TryParse(out value);
  // `Integer` vs `string` ------^
}

if(str is Integer(var value))

I suppose operator is would not be restricted to take the enclosing type as the first parameter, In contrast, conversion operators are restricted (CS0556).

Now, if I want to define it as an extension operator for the type int,

static class IntegerExtensions {
  public static bool operator is(this string str, out int value) => ... ;
}

It would not be clear that which type we are targeting. GetValues method also had this issue:

static class IntegerExtensions {
  public static bool GetValues(this string str, out int value) => ... ;
}

This is still an extension on type string and not int.

I think this needs something like #6136 to be clear about the intended target type.

extension Int32 {
   public static bool operator is(string str, out int value) => ... ;
}

I'm not aware of any plans for supporting extension is operators, though. But I do believe this will be really useful when you want to use positional patterns on types that don't already provide an operator is like expression trees (#10153 comment).

@gafter gafter added the Feature - Extension Everything The extension everything feature label Jun 3, 2016
@gafter
Copy link
Member

gafter commented Apr 28, 2017

Issue moved to dotnet/csharplang #515 via ZenHub

@gafter gafter closed this as completed Apr 28, 2017
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