Use int constant in attribute - c#

Can anybody explain why I can't use a const Int32 in an C# attribute?
Example:
private const Int32 testValue = 123;
[Description("Test: " + testValue)]
public string Test { get; set; }
Makes the compiler say:
"An attribute argument must be a constant expression, ..."
Why?

As the error states, an attribute argument must be a constant expression.
Concatenating a string and an integer is not a constant expression.
Thus, if you pass "Test: " + 123 directly, it will give the same error.
On the other hand, if you change testValue to a string, it will compile.
Explanation
The rules for constant expressions state that a constant expression can contain arithmetic operators, provided that both operands are themselves constant expressions.
Therefore, "A" + "B" is still constant.
However, "A" + 1 uses the string operator +(string x, object y);, in which the integer operand is boxed to an object.
The constant-expression rules explicitly state that
Other conversions including boxing, unboxing and implicit reference conversions of non-null values are not permitted in constant expressions.

'+' operator between int and string causes invocation of ToString method of int which is not "Compile time constant". It is like this
private const int x = 3;
private const string s = x.ToString(); // cannot be done

You can only pass compile time evaluated constants including result of typeof() operators and excluding decimals into Attributes constructors

Related

How does the C# compiler resolve types before applying a binary operator?

I'm working on a typed scripting language backed by C# Expression Trees. I'm stuck on one issue around proper type conversion with binary operators. Here is an example of the behavior I'm trying to mimic: (The conversion rules should be the same C#'s compiler)
var value = "someString" + 10; // yields a string
var value = 5 + "someString"; // also yields a string, I don't know why
var x = 10f + 10; // yields a float
var y = 10 + 10f; // also yields a float, I don't know why
How does the C# compiler know to call ToString() on the integer in the first line and to convert the integers to floats in both directions when adding with a float? Are these conversion rules hard coded?
My compiler basically works like this now for binary operators:
Expression Visit(Type tryToConvertTo, ASTNode node) {
// details don't matter. If tryToConvertTo is not null
// the resulting expression is cast to that type if not already of that type
}
// very simplified but this is the gist of it
Expression VisitBinaryOperator(Operator operator) {
Expression lhs = Visit(null, operator.lhs);
Expression rhs = Visit(lhs, operator.rhs); // wrong, but mostly works unless we hit one of the example cases or something similar
switch(operator.opType) {
case OperatorType.Add: {
return Expression.Add(lhs, rhs);
}
// other operators / error handling etc omitted
}
}
I know always accepting the left hand side's type is wrong, but I have no idea what the proper approach to resolving the example expressions might be other than hard coding the rules for primitive types.
If anyone can point me in the right direction I'd be very grateful!
This kind of questions can only be answered accurately via the language specification.
+ operator with string and int operands
https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1195-addition-operator
Here, under String concatenation you will see:
These overloads of the binary + operator perform string concatenation. If an operand of string concatenation is null, an empty string is substituted. Otherwise, any non-string operand is converted to its string representation by invoking the virtual ToString method inherited from type object. If ToString returns null, an empty string is substituted.
+ operator with float and int operands
The int here is implicitly converted to float, specified here:
https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/conversions.md#1023-implicit-numeric-conversions

sbyte and if operator

Why the first statement is allowed but not the second in the below sample code?
sbyte test1 = true ? 1 : -1; // Allowed
sbyte test2 = "a".Equals("b") ? 1 : -1; // Not allowed
I checked that all .Equals(..) overloads for string return a bool.
The first statement includes a constant expression that can be evaluated at a compile-time. It's equivalent to this:
sbyte test1 = 1;
Technically, this is an assignment of an int literal (1) to an sbyte variable. But the compiler is smart enough to figure out that the value is small enough to fit into an sbyte range and allows an implicit conversion, i.e. you don't need to cast it to int.
The second statement includes a method call and those are only evaluated at runtime. In other words, the compiler isn't smart enough to simplify an expression. The only thing it knows is that this impression returns an unknown int value and those should be converted explicitly. For example, like this:
sbyte test2 = (sbyte) ("a".Equals("b") ? 1 : -1);
All of this is explained in the C# specification. See, Implicit constant expression conversions:
A constant_expression (Constant expressions) of type int can be converted to type sbyte, byte, short, ushort, uint, or ulong, provided the value of the constant_expression is within the range of the destination type.

Operator ">" cannot be applied to type 'ulong' and 'int'

I'm curious to know why the C# compiler only gives me an error message for the second if statement.
enum Permissions : ulong
{
ViewListItems = 1L,
}
public void Method()
{
int mask = 138612833;
int compare = 32;
if (mask > 0 & (ulong)Permissions.ViewListItems > 32)
{
//Works
}
if (mask > 0 & (ulong)Permissions.ViewListItems > compare)
{
//Operator '>' cannot be applied to operands of type 'ulong' and 'int'
}
}
I've been experimenting with this, using ILSpy to examine the output, and this is what I've discovered.
Obviously in your second case this is an error - you can't compare a ulong and an int because there isn't a type you can coerce both to. A ulong might be too big for a long, and an int might be negative.
In your first case, however, the compiler is being clever. It realises that const 1 > const 32 is never true, and doesn't include your if statement in the compiled output at all. (It should give a warning for unreachable code.) It's the same if you define and use a const int rather than a literal, or even if you cast the literal explicitly (i.e. (int)32).
But then isn't the compiler successfully comparing a ulong with an int, which we just said was impossible?
Apparently not. So what is going on?
Try instead to do something along the following lines. (Taking input and writing output so the compiler doesn't compile anything away.)
const int thirtytwo = 32;
static void Main(string[] args)
{
ulong x = ulong.Parse(Console.ReadLine());
bool gt = x > thirtytwo;
Console.WriteLine(gt);
}
This will compile, even though the ulong is a variable, and even though the result isn't known at compile time. Take a look at the output in ILSpy:
private static void Main(string[] args)
{
ulong x = ulong.Parse(Console.ReadLine());
bool gt = x > 32uL; /* Oh look, a ulong. */
Console.WriteLine(gt);
}
So, the compiler is in fact treating your const int as a ulong. If you make thirtytwo = -1, the code fails to compile, even though we then know that gt will always be true. The compiler itself can't compare a ulong to an int.
Also note that if you make x a long instead of a ulong, the compiler generates 32L rather than 32 as an integer, even though it doesn't have to. (You can compare an int and a long at runtime.)
This points to the compiler not treating 32 as a ulong in the first case because it has to, merely because it can match the type of x. It's saving the runtime from having to coerce the constant, and this is just a bonus when the coercion should by rights not be possible.
It's not the CLR giving this error message it's the compiler.
In your first example the compiler treats 32 as ulong (or a type that's implicitly convertible to ulong eg uint) whereas in your second example you've explicitly declared the type as an int. There is no overload of the > operator that accepts an ulong and an int and hence you get a compiler error.
rich.okelly and rawling's answers are correct as to why you cannot compare them directly. You can use the Convert class's ToUInt64 method to promote the int.
if (mask > 0 & (ulong)Permissions.ViewListItems > Convert.ToUInt64(compare))
{
}

Why does this implicit conversion from int to uint work?

Using Casting null doesn't compile as inspiration, and from Eric Lippert's comment:
That demonstrates an interesting case. "uint x = (int)0;" would
succeed even though int is not implicitly convertible to uint.
We know this doesn't work, because object can't be assigned to string:
string x = (object)null;
But this does, although intuitively it shouldn't:
uint x = (int)0;
Why does the compiler allow this case, when int isn't implicitly convertible to uint?
Integer constant conversions are treated as very special by the C# language; here's section 6.1.9 of the specification:
A constant expression of type int can be converted to type sbyte, byte, short, ushort, uint, or ulong, provided the value of the constant-expression is within the range of the destination type. A constant expression of type long can be converted to type ulong, provided the value of the constant expression is not negative.
This permits you to do things like:
byte x = 64;
which would otherwise require an ugly explicit conversion:
byte x = (byte)64; // gross
The following code wil fail with the message "Cannot implicitly convert type 'int' to 'uint'. An explicit conversion exists (are you missing a cast?)"
int y = 0;
uint x = (int)y;
And this will fail with: "Constant value '-1' cannot be converted to a 'uint'"
uint x = (int)-1;
So the only reason uint x = (int)0; works is because the compiler sees that 0 (or any other value > 0) is a compile time constant that can be converted into a uint
In general compilers have 4 steps in which the code is converted.
Text is tokenized > Tokens are parsed > An AST is built + linking > the AST is converted to the target language.
The evaluation of constants such as numbers and strings occurs as a first step and the compiler probably treats 0 as a valid token and ignores the cast.

Typecasting in C#

What is type casting, what's the use of it? How does it work?
Casting is usually a matter of telling the compiler that although it only knows that a value is of some general type, you know it's actually of a more specific type. For example:
object x = "hello";
...
// I know that x really refers to a string
string y = (string) x;
There are various conversion operators. The (typename) expression form can do three different things:
An unboxing conversion (e.g. from a boxed integer to int)
A user-defined conversion (e.g. casting XAttribute to string)
A reference conversion within a type hierarchy (e.g. casting object to string)
All of these may fail at execution time, in which case an exception will be thrown.
The as operator, on the other hand, never throws an exception - instead, the result of the conversion is null if it fails:
object x = new object();
string y = x as string; // Now y is null because x isn't a string
It can be used for unboxing to a nullable value type:
object x = 10; // Boxed int
float? y = x as float?; // Now y has a null value because x isn't a boxed float
There are also implicit conversions, e.g. from int to long:
int x = 10;
long y = x; // Implicit conversion
Does that cover everything you were interested in?
Casting means creating a reference to an object that is of a different type to the reference you're currently holding. You can do upcasting or downcasting and each has different benefits.
Upcasting:
string greeting = "Hi Bob";
object o = greeting;
This creates a more general reference (object) from the more specific reference (string). Maybe you've written code that can handle any object, like this:
Console.WriteLine("Type of o is " + o.GetType());
That code doesn't need to be changed no matter what objects you set o to.
Downcasting:
object o = "Hi Bob";
string greeting = (string)o;
Here you want a more specific reference. You might know that the object is a string (you can test this e.g.:
if (o is string)
{ do something }
Now you can treat the reference as a string instead of an object. E.g. a string has a length (but an object doesn't), so you can say:
Console.WriteLine("Length of string is " + greeting.length);
Which you can't do with an object.
See this or this:
Because C# is statically-typed at compile time, after a variable is declared, it cannot be declared again or used to store values of another type unless that type is convertible to the variable's type
...
However, you might sometimes need to copy a value into a variable or method parameter of another type. For example, you might have an integer variable that you need to pass to a method whose parameter is typed as double. Or you might need to assign a class variable to a variable of an interface type. These kinds of operations are called type conversions. In C#, you can perform the following kinds of conversions
Casting from one data type to another.
For a general reading see this.
See also msdn
Also, if you're explicitly casting, you can take advantage of pattern matching. If you have an object:
object aObject = "My string value";
You can safely cast the object as a string in a single line:
if (aObject is string aString)
{
Console.WriteLine("aString = " + aString)
// Output: "aString = My string value"
}
Using this, along with an inverted if statement, you can safely cast types, and fail out early if need be:
public void Conversion(object objA, object objB)
{
// Fail out early if the objects provided are not the correct type, or are null
if (!(objA is string str) || !(objB is int num)) { return; }
// Now, you have `str` and `num` that are safely cast, non-null variables
// all while maintaining the same scope as your Conversion method
Console.WriteLine("str.Length is " + str.Length);
Console.WriteLine("num is " + num);
}

Categories

Resources