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: "static cast" operator for C# #1203

Closed
ViIvanov opened this issue Mar 11, 2015 · 26 comments
Closed

Proposal: "static cast" operator for C# #1203

ViIvanov opened this issue Mar 11, 2015 · 26 comments

Comments

@ViIvanov
Copy link

It would be nice to have a "static cast" operator in C#.

Existing type cast operator looks similar in cases, when it throws InvalidCastException (or other from user defined type cast operator, for example) and when it not throws (converting to a base type or to an interface or enum type to an underlying type).

I.e. "static cast" is the cast that can be produced without exceptions.

Example:

// We have a few overloards…
void M(IList<int> x) { } // 1
void M(IReadOnlyList<int> x) { } // 2
void M(IList<string> x) { } // 3
void M(IReadOnlyList<string> x) { } // 4

// … and we want to call one of them in our X method:
void X1(Collection<int> x) {
  // Error CS0121 The call is ambiguous between the following methods or properties…
  M(x);
}

void X2(Collection<int> x) {
  // Looks is not good because…
  M((IList<int>)x);

  // 1. (IList<int>)x looks like an expression, that can throws an exception
  // 2. it is not difficult to write 
  M((IList<string>)x);
  // and code will be successfully compiled and may not work in runtime!
}

void X3(Collection<int> x) {
  // I think, calls in next lines more clearly than in X2 above
  // and syntax with colon is clear and intuitive.
  M(x : IList<int>);
}
@dsaf
Copy link

dsaf commented Mar 12, 2015

  1. The idea makes sense, but is there a more realistic example? Those overloads do not look like good anyway.
  2. The suggested syntax looks too much like TypeScript IMHO - could be confusing with the upcoming record type syntax as well.

@svick
Copy link
Contributor

svick commented Mar 12, 2015

I think the biggest problem with the syntax is that it's ambiguous with named parameters. Does M(color : Color) mean "the value of the parameter named color is Color" or "the value of the first parameter is color, statically cast to the type Color"?

@dsaf
Copy link

dsaf commented Mar 12, 2015

Most intuitive would be saying 'as' or 'like':

M(x like IList<int>);

@leppie
Copy link
Contributor

leppie commented Mar 12, 2015

How about:

M(IList<int>(x));

@dsaf
Copy link

dsaf commented Mar 12, 2015

@leppie

I would drop the brackets than:

M(IList<int> x);

@leppie
Copy link
Contributor

leppie commented Mar 12, 2015

@dsaf But then you have to add extra syntax rules to the parser which could cause ambiguity down the line (cant think of anything off the top of my head, but you never know).

@paulomorgado
Copy link

@ViIvanov, I'm trying very hard to understand the value of this proposal.

Other than the way you write the cast, is there any thing new? Any motivation?

@dsaf
Copy link

dsaf commented Mar 13, 2015

@paulomorgado, did you read the whole of X2 body? It's not a feature suggestion, it's a fix to existing language design.

@paulomorgado
Copy link

@dsaf, a fix is supposed to be a solution to something that is broken.

I'm failing to understand what it is broken and what the solution is.

@dsaf
Copy link

dsaf commented Mar 13, 2015

@paulomorgado, did you read the X2 method carefully? Why is it necessary to use an unsafe cast to resolve the method ambiguity? It can lead to bugs as the code changes.

Related reading:

http://en.wikipedia.org/wiki/Type_conversion

http://en.wikipedia.org/wiki/Function_overloading

https://msdn.microsoft.com/en-us/library/aa691336%28v=vs.71%29.aspx

@paulomorgado
Copy link

@dsaf , thank for clarifying that the problem is in my reading and not on the writting.

So, if I understand it correctly, the problem is that the cast ((IList<int>)x) might fail at run time, but you want the "conversion", not the "conversion if" (x as IList<int>).

The proposal is to write this:

void X(Collection<int> x)
{
    IList<int> y = x;
    M(y);
}

in a more succint way, like many other C# features.

If declaration expressions are introduced, we will be able to write that as:

void X(Collection<int> x)
{
    M(IList<int> y = x);
}

That poses an interesting problem regarding syntax. I wonder if the compiler could handle M(x: x : IList<int>).

For the present exemples, naming the parameters diferently would also be a solution:

void M(IList<int> l) { } // 1
void M(IReadOnlyList<int> rl) { } // 2
void M(IList<string> l) { } // 3
void M(IReadOnlyList<string> rl) { } // 4

void X(Collection<int> x)
{
    M(l: x);
}

But that's still working around the problem, not solving it.

@ViIvanov
Copy link
Author

I'm sorry for a late answer.

@dsaf, @svick

  1. OK, I will find real examples from my code base. May be code below?
  2. I'm OK with other syntax of this operator. Yes, with named arguments colon can be hard.

@paulomorgado
You are right, in a this case a strongly typed local variable now (without static cast operator) is better. But, what about this example in a linq query?

var items =
    from item in GetItems()
    where SomeFilter((IList<int>)item.Fields)
    select item;

We can not use explicitly typed local variable here.

A joke I use a next code for this:

var items =
    from item in GetItems()
    where SomeFilter(item.Fields ?? default(IList<int>))
    select item;

It's work, but not clear.

@ViIvanov
Copy link
Author

@paulomorgado In other words, a static cast operator will be useful in cases, where we can not introduce an explicitly typed local variables or in cases where is to hard to invent a names for these local variables.

@ViIvanov
Copy link
Author

May be, :> is better?

SomeMethod(arg :> SomeType);

@ViIvanov
Copy link
Author

@paulomorgado
Main concern of static cast operator is tell that we do not need any special conversion operations. We just need to clarify a static type of the expression.

I think, it makes a reading a code simpler, intentions of author of code more explicit.

Local variables is not a good because

  1. Author should invent a good names for these variables.
  2. Refactoring of code with it variables is harder
  3. Readers should know - why author introduces local variable. I think it is not absolutely clear.

@ViIvanov
Copy link
Author

@leppie

If "x" is not a simple expression

M(IList<int>(x1 + x2.Mmm(sss)));

the result IMHO is a not good. I think

M((x1 + x2.Mmm(sss)) :> IList<int>);

better, when expression and the proposed type of expression visually separated from each other, peer to peer.

@dsaf
Copy link

dsaf commented Mar 13, 2015

I doubt that C# team would be open to introducing a new operator (:>) for a relatively small problem like this. Even for pattern matching proposal they tried to re-use the switch statement as much as possible.

Is it inspired by F#'s one? https://msdn.microsoft.com/en-us/library/dd233220.aspx
It feels a bit alien to C#.

@ViIvanov
Copy link
Author

@dsaf
Agree. It just purposes. I think a : is a better and code like next

class x { }
class y : x { }

static void M(x x) { }
static void M(y x) { }

static void Main() {
  var x = new y();

  // What does it mean?
  // First "x" for the name of a parameter
  // Second "x" for the argument
  // or
  // First "x" for for the argument
  // Second "x" for the explicit type
  M(x : x); 

  // First "x" for the name of a parameter
  // Second "x" for the argument
  // Third "x" for the explicit type
  M(x : x : x);
}

can be resolved by a compiler. I think it's clear it mean a named parameter or it can be a compiler error.

@Porges
Copy link

Porges commented Apr 27, 2016

I really want a "safe upcast" operator like this.

Here's another use-case that comes up a lot for me:

public class C
{
    public C(params int[] values)
        : this((IEnumerable<int>)values)
    {
    }

    public C(IEnumerable<int> r)
    {
        // real constructor
    }
}

(Of course, this could also be solved by enumerable params, but...)

It annoys me that I have to use the sledgehammer cast operator when I just want a compiler-proven-safe upcast to the base type.

Not overly found of the syntax. But the best syntax (as) has already been stolen... safeas? as2? asEx? 😄

@alrz
Copy link
Contributor

alrz commented Apr 3, 2017

Using "attributes everywhere" (https://github.com/dotnet/roslyn/issues/6671) this can be implemented as an analyzer,

[SourceAttributeUsage(SourceAttributeTargets.Cast)]
class StaticCastAttribute {}

 M([StaticCast](IList<int>)x);

@whoisj
Copy link

whoisj commented Apr 3, 2017

I'm sorry, perhaps I'm pedantic: what is wrong with as?

@alrz
Copy link
Contributor

alrz commented Apr 3, 2017

@whoisj It allows you to do a "down cast" while OP wants to do just a "up cast".

@whoisj
Copy link

whoisj commented Apr 3, 2017

@whoisj It allows you to do a "down cast" while OP wants to do just a "up cast".

Ahh yes - thanks for that. I think I've been thinking C# too long now and I don't even attempt to "up cast" any more. 😏

@gafter
Copy link
Member

gafter commented Apr 4, 2017

We are now taking language feature discussion in other repositories:

Features that are under active design or development, or which are "championed" by someone on the language design team, have already been moved either as issues or as checked-in design documents. For example, the proposal in this repo "Proposal: Partial interface implementation a.k.a. Traits" (issue 16139 and a few other issues that request the same thing) are now tracked by the language team at issue 52 in https://github.com/dotnet/csharplang/issues, and there is a draft spec at https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md and further discussion at issue 288 in https://github.com/dotnet/csharplang/issues. Prototyping of the compiler portion of language features is still tracked here; see, for example, https://github.com/dotnet/roslyn/tree/features/DefaultInterfaceImplementation and issue 17952.

In order to facilitate that transition, we have started closing language design discussions from the roslyn repo with a note briefly explaining why. When we are aware of an existing discussion for the feature already in the new repo, we are adding a link to that. But we're not adding new issues to the new repos for existing discussions in this repo that the language design team does not currently envision taking on. Our intent is to eventually close the language design issues in the Roslyn repo and encourage discussion in one of the new repos instead.

Our intent is not to shut down discussion on language design - you can still continue discussion on the closed issues if you want - but rather we would like to encourage people to move discussion to where we are more likely to be paying attention (the new repo), or to abandon discussions that are no longer of interest to you.

If you happen to notice that one of the closed issues has a relevant issue in the new repo, and we have not added a link to the new issue, we would appreciate you providing a link from the old to the new discussion. That way people who are still interested in the discussion can start paying attention to the new issue.

Also, we'd welcome any ideas you might have on how we could better manage the transition. Comments and discussion about closing and/or moving issues should be directed to #18002. Comments and discussion about this issue can take place here or on an issue in the relevant repo.


I am not moving this particular issue because I don't have confidence that the LDM would likely consider doing this because it can be done in an API.

public static T StaticCast<T>(T value) => value;

@gafter gafter closed this as completed Apr 4, 2017
@ViIvanov
Copy link
Author

ViIvanov commented Apr 5, 2017

@gafter Thanks. In case I will found some other arguments for this operator I will open new discussion in dotnet/csharplang repo.

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