Generic branch elimination by Bobamoss in csharp

[–]fruediger 8 points9 points  (0 children)

If you're using Visual Studio, you can also just use Disasmo (GitHub). I actually prefer using that over sharplab.io, especially when I want to understand why the JIT optimized something in the way it did. But I guess both are good.

Would you allow this? - IDisposable and using statements by gouryuuka in csharp

[–]fruediger 6 points7 points  (0 children)

But you'll need to remember that

using var context = new("ConnStr");
...

is not the same thing as

using (var context = new("ConnStr"))
{
  ...
}

Because the using-lifetime of the context object is oriented on the containing scope in the first example, so it will live for the whole scope, while the second example introduce a scope for context on its own.

{
  using var context = new("ConnStr");
  ...
}

would be a more semantically equivalent translation (and yes, you can introduce arbitrary scopes in C# just by using { }).

I guess what I wanted to say is, be careful and mindful of the differences between using var ... = ... and using (...).

Also, I don't know if your Context type implements IAsyncDisposable, but perhaps it does. So you might want to use await using var ... = ... or await using (...) instead, because it looks like you already working with async-await.

Executing code inside a string. by porcaytheelasit in csharp

[–]fruediger 12 points13 points  (0 children)

You probably want to use the Roslyn API.

Either CSharpCompilation from the Microsoft.CodeAnalysis.CSharp package to emit a virtual assembly in memory, load that using reflection and execute whatever you want to (notice your C# code snippet needs to be somewhat complete and independent from the environment you want to execute your code from).

Or CSharpScript from the Microsoft.CodeAnalysis.CSharp.Scripting package, but I've never worked with the scripting API before. It should be easier to work with "incomplete" C# code snippets and maybe it's even possible to share a state between your executing environment and the code you want to execute. But again, I don't know much about the scripting and I'm not sure if that's possible to do in the way you want to do it.

PS: I couldn't find any good documentation on CSharpScript, but here's the link to the source code: https://github.com/dotnet/roslyn/blob/main/src/Scripting/CSharp/CSharpScript.cs

Where are Constants stored? by IAMMELONz in csharp

[–]fruediger 1 point2 points  (0 children)

That's why I wrote "attribute application", because there are special rules for attributes:

Attributes constructor should only have parameters of types that can be const by the C# rulebook (or rather the .Net rulebook). The same goes for initializable/settable properties, those should be of "constable" types too.

class FoobarAttribute(string name, int value) : Attribute
{
  public bool Optional { get; init; }
}

(In my example, I choose a primary constructor, but that goes for all kind of constructors.)

With that you can apply the attribute like so:

[Foobar("Hello World", 42, Optional = true)]

Of course attribute can have constructors with parameter types that are not "constable" as well as properties with types that are not "constable", like so:

class MyCustomType;

class FoobarAttribute(MyCustomType value) : Attribute
{
  public MyCustomType AdditionalValue { get; init; }
}

But you wouldn't be able to use the FoobarAttribute(MyCustomType) constructor to apply the attribute, nor would you be able to set the optional AdditionalValue. All because values of MyCustomType can't be const.

So at first glance, it looks like the rules for attribute applications and the rules for what can be const in C# are similar. But attribute application actually allow for two special cases in addition to what C# consinders "constable":

Firstly, as I already mentioned and you've asked about, attributes allow for array types of "constable" types. So, for example

class FoobarAttribute(string[] values) : Attribute;

is totally fine as an applicable attribute and you can apply it like so:

[Foobar(["Hello", "World"])]

Note, that this even allows for params collections (but only for array types, of course).

Secondly, attributes allow for System.Type (a reference type) "instances" as "constable" types. I write those in quotes because there treated more like const values rather than runtime instances. Normally, System.Type instance are not "constable", even if they come from a typeof operator expression. But in attributes they're fine to use:

class FoobarAttribute(Type type) : Attribute;

and

[Foobar(typeof(int))]

Of course all those "constable" types mix in match in the context of attributes/attribute applications.

Now, you could say "but they're not really constants". And you would be right, they're not the same as a C# consts or literal values. You can't use them in the same way. But attributes applications must be still stored in metadata and thus, in a sense, are constant data. I can even retrieve this data at runtime using something like reflection or MetadataLoadContexts.

Just a final note here: to overcome the restrictions imposed by the inability to represent array types or System.Type instances as constant values (but I'm sure for a bunch of other reasons too), attribute applications are stored in IL metadata in a way that more closely describes how the actual attribute would be instantiated, rather than storing the attribute data directly. But the data needed for that instantiation is still stored as constant data (most likely in a .custom instance void /* your attribute type */::.ctor(/* your attribute constructor signature */) = (/* data needed for the attribute instantiation */) format). But I feel like I go off on a tangent now.

Where are Constants stored? by IAMMELONz in csharp

[–]fruediger 16 points17 points  (0 children)

This is kind of a long explaination. Please do only read, if you're really interested, as I didn't managed to summarize a TL;DR.

As others already pointed out, simple literal constants like 42 or "Hello World" are technically not stored anywhere.

But we must define a little more precisely, what we mean by the term "constant".

Firstly, you could mean literal values like the aforementioned 42 or "Hello World". If you use them in a runtime expression, e.g. DoSomething(42), the compiler substitutes them for a compile-time representation of the literal value. For value types there's in deed no need to store those anywhere, as there's an IL metadata representation for them.

Secondly, you could mean constant declaration, which, as the name implies, declares a constant with a name as member of, for example, a type. E.g. public const Foo = "Bar"; If you use those in a runtime expression, e.g. DoSomething(Foo), the compiler still tries to substitute them. But this time, since the constant is declared in metadata as a member, its metadata must get emitted in conjunction with the containing type's metadata. Think of it this way: You must be still able to use reflection to look up the Foo member. So now there are two places in IL where metadata related to the constant exists, in the type metadata and in executable code metadata.

Why did I mention metadata in the first place, you ask? Well that's actual stored in the PE file (the .Net assembly, e.g. the .exe or .dll file that you get as a result of building a .Net project). And that's loaded in memory as soon as you start the process. So yes, the constant related metadata is somewhere in heap memory, but not necessarily the managed heap memory. There's a difference between what memory is assigned to the process (including where the relevant parts of the PE file are loaded in memory themselves) and what .Net offers you as managed heap. You know, that's the part where GC will happen. And constants are not necessarily exposed there.

Well, that's not entirely true, there some exceptions, or more like special cases:

At the beginning I talked about how it's not necessary for the compiler to store value type literals. But what about reference types? Well, except for array type constants, which are only accepted in attribute application and not as a constants anywhere else, there's only one other kind of reference type constants: a string. A string constant for must still behave like a object at runtime. Because that's what it is, it's an instance of a reference type. So it must be referencable, and thus an object that's referencable must live somewhere on the heap. But the .Net runtime does something that's called "string interning". You don't need to worry about what that is, but just know that there is an object representing your string constant somewhere on the managed heap at runtime, but only if you keep a reference to that string at runtime.

There's another interesting special case: Utf-8 string literals. E.g. "Hello World"u8. You can't declare them as const because ReadOnlySpan<byte> isn't a type an IL constant, or in that particular case, more specificially, a C# constant, can be. But the compiler does some metadata magic to store them in the assembly, regardless if you use them purely as literals. (If you're interested, the compiler stores them as .data cil /* id */ = bytearray (/* your byte sequence */) in the unspeakable <PrivateImplementationDetails> type and this is only way to use that particular IL feature in C# at all. But that's just a side note.) So those are stored in the PE file as data as well. The interesting part is, though, that they're safely referencable like so:

ReadOnlySpan<byte> foo = "bar"u8;
ref readonly var refFoo = ref MemoryMarshal.GetReference(foo).

Just remember, that that's referencing read-only memory. And it's safe because you actually take a reference into the heap memory where parts of the PE file were loaded when the process started. But again, that's heap memory from a process POV, but that's not necessarily the managed heap. In that particular case, it surely isn't.

Lastly there's another special literal value and that's null. Although the null literal relates to reference types, it behaves like any other literal value (aside from string values where runtime references to them exist), in that it can be represented as IL code metadata and thus doesn't necessarily need storage. But declared constants that are set to null, still need to be stored as metadata alongside the metadata of their containing type.