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

Created on 12 Mar 2015  ·  26Comments  ·  Source: dotnet/roslyn

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>);
}
0 - Backlog Area-Language Design Language-C#

Most helpful comment

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 https://github.com/dotnet/roslyn/issues/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.

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

All 26 comments

  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.

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"?

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

M(x like IList<int>);

How about:

M(IList<int>(x));

@leppie

I would drop the brackets than:

M(IList<int> x);

@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).

@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?

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

@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.

@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

@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.

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.

@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.

May be, :> is better?

SomeMethod(arg :> SomeType);

@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.

@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.

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#.

@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.

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? :smile:

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);

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

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

@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. 😏

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 https://github.com/dotnet/roslyn/issues/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.

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

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

Was this page helpful?
0 / 5 - 0 ratings