Replies: 61 comments 224 replies
-
I found myself wanting a static keyword for inline expressions this week. This would be great to have. |
Beta Was this translation helpful? Give feedback.
-
Coming from a VB6 & VB.NET background, this is one thing I miss a lot even after over 7 years of programming with C#. I strongly believe that this is a must-have! |
Beta Was this translation helpful? Give feedback.
-
To address your questions:
The VB.NET implementation does not initialize the value until the first time it is used (in the method in which it is located). Doing the same thing in C# will defer potentially expensive initializations until when it is really needed.
Drawing again from the VB.NET implementation, I'd say it depends on where the variable is created.
I'm not really sure of what you mean here. I'll just state that the VB.NET version doesn't allow the keyword to be combined with It makes sense to disallow combining it with EDITFor cross-reference, here is a link to the documentation on the |
Beta Was this translation helpful? Give feedback.
-
I would say the following:
*This rule could be omitted altogether with some alternative syntax I propose at the bottom of this post. Examples: public class C
{
public static string[] M1(string str)
{
//static method: lifts to static field
static var splitchar = new [] { External.GetRandomChar() };
return str.Split(splitchar);
}
public string[] M2(string str)
{
//instance method: lifts to instance field
static var splitchar = new [] { External.GetRandomChar() };
return str.Split(splitchar);
}
public string[] M3(string str)
{
//data is statically known: may be lifted to static field
static var splitchar = new [] { '.' };
return str.Split(splitchar);
}
public string[] M4(string str)
{
//lifts to non-readonly instance field, but makes next line illegal
static readonly var splitchar = new [] { External.GetRandomChar() };
splitchar = new [] { External.GetRandomChar() }; //ERROR
return str.Split(splitchar);
}
private readonly ICharGenerator _generator;
public C(ICharGenerator generator)
{
_generator = generator;
//{A}
}
public string[] M5(string str)
{
//lifts to readonly instance field, assignment is placed in ctor on line marked with {A}
static readonly var splitchar = new [] { _generator.GetRandomChar() };
return str.Split(splitchar);
}
public string[] M6(string str)
{
//lifts to readonly field, potentially static, assignment is a field initializer
static readonly var splitchar = new [] { ':' };
return str.Split(splitchar);
}
} Open question: Syntax? I'm not sold on what's proposed here. It seems to me like the variable is constantly re-assigned every time the method runs, which only makes it look like the very problem it's trying to solve. Possible alternative: public string[] M(string str)
static var splitchar = new [] { External.GetRandomChar() }
{
return str.Split(splitchar);
} I think this syntax neatly represents the "in-between" nature of these variables. They're really a field, declared outside of the method, but they're only accessible inside the method. A lot less mental gymnastics, in my opinion. Open question: Are we married to the keyword It would be symmetrical with VB.NET, but it may overload the
EDIT: After a little bit of thought, with that proposed syntax, why even need a keyword? You should just be able to move an existing field declaration (without access modifiers) to right in front of a method body to make it only accessible in that method. Or the other way around if you want it to be a higher accessible field again. public string[] M1(string str)
//we're still going to get 'var' for fields, aren't we? let's just pretend we already have that
var splitchar = new [] { External.GetRandomChar() } //instance field
{
return str.Split(splitchar);
}
public string[] M2(string str)
var splitchar = new [] { '.' } //force instance field, even if it could be static, IDE can suggest to make it static
{
return str.Split(splitchar);
}
public string[] M3(string str)
static var splitchar = new [] { External.GetRandomChar() } //force static field
{
return str.Split(splitchar);
}
public string[] M4(string str)
static var splitchar = new [] { _generator.GetRandomChar() }; //error CS0120
{
return str.Split(splitchar);
} Now the thing left on my mind is "Do we need the semi-colon?" |
Beta Was this translation helpful? Give feedback.
-
I'd be happier with an |
Beta Was this translation helpful? Give feedback.
-
In general, I think @Joe4evr's suggestion is in line with mine with the only difference being about the combination with the
Thinking it through once again, I can possibly see why we may want to allow combining with the Does applying a I feel like putting a Note that I am not against allowing for a ‾‾‾‾‾‾ |
Beta Was this translation helpful? Give feedback.
-
It will be when #188 becomes a reality. |
Beta Was this translation helpful? Give feedback.
-
Sorry, I didn't know that such an idea had been proposed.
In that case, I think your suggestion can pass without need for any further
discussion.
|
Beta Was this translation helpful? Give feedback.
-
Miss this since I was using PoC |
Beta Was this translation helpful? Give feedback.
-
@Joe4evr:
From a readability standpoint, I find your new syntax quite difficult to comprehend.
How do you suppose multiple static variables would be created? One per line? using a delimiter (which one)?
Most importantly, wouldn't your new syntax be too much of a breaking change?
I strongly believe that the way to go on this is to simply introduce a new keyword or adapt an existing one for use in this situation.
|
Beta Was this translation helpful? Give feedback.
-
I would love to have this one in the language. The keyword should be "static". Here's the analogy. One can declare a class level "string foo;", and foo is available to the whole class. If "string foo;" is declared inside of a function, it is only accessible in the function. At the class level, one can declare "static string foo;"-- it is a static variable, but it is only accessible inside the class. In a function, you should be able to declare "static string foo;". It is statically scoped, but it can only be accessed from inside the function. This language tool is great for implementing singleton patterns. The Instance() function would declare "static MyClass _instance = new MyClass();" in the function itself and return it. This prevents others methods of the class from using _instance instead of Instance(). Statically typed variables are dangerous. Keeping all usage of a static method in one small function adds greatly to code clarity. |
Beta Was this translation helpful? Give feedback.
-
I can see this replacing a lot of Lazy class usage, which would be nice. |
Beta Was this translation helpful? Give feedback.
-
While I see the benefit of using |
Beta Was this translation helpful? Give feedback.
-
This is similar to local functions, you can bring statics closer to their usage with a scoped name. I mentioned inline syntax to explore alternatives, that's not necessarily a "better" way to do this. |
Beta Was this translation helpful? Give feedback.
-
Static locals are nice for declaring reusable (across calls) method-scoped variables that can't be consts.
Imagine a class with 10 of the (2)-like functions. The scope gets polluted really quickly. To avoid naming conflicts, I name statics "HypotheticalFunction1_lut" or "tlsHypotheticalFunction2_store"... this is really gross. This is incredibly common for me - I'll have a function that allocates 10 collections of different types. I'll want to reuse 10 collections across calls. Guess what? Now I have 10 class-scoped statics. 😢
My 3rd case would probably be better fixed with language support for constexpr functions. In any case, note how fluid changing from |
Beta Was this translation helpful? Give feedback.
-
Note: any design here would have to establish the expected behavior of what a static local means in a generic method. I would assume it would be the same as a generic type, and each distinct instantiation would get its own storage location. This makes a static local not akin to a field in the containing type, but rather a field inside a nested type which has the same generic type args as the method it is accessed from. in other words: void M<X, Y>(int i)
{
static int static_local = ...;
} would be the same as: void M<X, Y>(int i)
{
StaticLocals<X, Y>.mangled_name_static_local = 1;
}
private class StaticLocals<T1, T2>
{
// ...
} (potentially with some concept of init-once or not). |
Beta Was this translation helpful? Give feedback.
-
Another very important and helpful use case: anonymous types and LINQ expressions. Together with partial generic arguments, it makes the following possible. This was inspired by this discussion 5381. void Test(Expression<Func<int, bool>> predicate) {}
void Foo()
{
// the following isn't about anonymous types and doesn't need partial generics
static readonly Expression<Func<int, bool>> isOdd = x => x % 2 == 0;
Test(isOdd);
bool flag = true;
static readonly Expression<Func<int, bool>> isOddOrEven = x => x % 2 == 0 ^ flag; // ERROR: static field cannot reference transient method local
// the following also needs partial generics
static readonly Expression<Func<Person, _>> selector = x => new { FullName = x.FirstName + " " + x.LastName };
var parties = this._personsQuery.Select(selector).ToList();
} Also in a separate exchange with @HaloFour, he suggested a new public event EventHandler Event
{
static int staticField;
instance EventHandler v;
add { v += value; ++staticField; } remove { v -= value; --staticField; }
raise { v?.Invole(sender, e); } // basing on delegate type, the parameters are created implicitly, using the names specified by delegate type
} Another minor details: in overloaded methods scenario, static locals will need a new class for each overload. Basically each method, regardless of generic parameters and regular parameters, needs a separate class. This also serves the important purpose of ensuring delayed initialization for static fields. void Foo(int a)
{
static int x = 1; // nested static class <qwe1>Foo_Statics
}
void Foo(string a)
{
static int x = 1; // nested static class <qwe2>Foo_Statics
} Can somebody please champion this? |
Beta Was this translation helpful? Give feedback.
-
@TahirAhmadov said:
I think that's absolutely right. Luckily, it's totally possible to cover those needs nice and explicitly with existing C# syntax. Therefore I'm pretty against adding a bunch of language-level complexity for this seemingly rare case that'd just be yet another way to achieve almost exactly the same thing with slightly different syntax. using System;
using System.ComponentModel;
using System.Threading;
public sealed class LazilyInitialized<T>
{
private T _value;
private object _lock;
private bool _isInitialized;
public ref T EnsureInitialized(Func<T> init)
{
LazyInitializer.EnsureInitialized(ref _value, ref _isInitialized, ref _lock, init);
return ref _value;
}
}
public class SomeExampleClass
{
public static void Main()
{
var instance = new SomeExampleClass();
Console.WriteLine(instance.Counter);
Console.WriteLine(instance.Counter);
}
// To avoid cluttering up intellisense
[EditorBrowsable(EditorBrowsableState.Never)]
// Declared at the scope it exists in, but no-one can access the value without ensuring it's initialized
private LazilyInitialized<long> _counter = new();
public long Counter
{
get
{
// Use whatever initialization logic you want here
// It's lazy and executed at maximum 1 time per instance
ref var localCounter = ref _counter.EnsureInitialized(() => LongRunningFunction());
// Use of the variable otherwise is *not* thread safe (as in VB)
localCounter += 10;
return localCounter - 1L;
}
}
public static long LongRunningFunction()
{
long i = 1L;
while (i < 100000L)
i += 1L;
return i;
}
} If you really wanted to stop other methods being able to use it at all, you can use existing mechanisms such as pulling the field and method into their own class, or a base/sub class depending on dependencies of the method in question. |
Beta Was this translation helpful? Give feedback.
-
In terms of syntax, I find it a bit awkward that the field init code is directly inside the method block but only runs the first time the method is called and is skipped on subsequent calls. What if there was a way to separate the field stuff, something like: void IncrementCounters()
{
fieldinit {
int counter = 0;
static int s_counter = 0;
// Allows for more complex multi-line initialization as well:
SomeClass field = new();
field.DoSomeInitStuff();
};
counter++;
s_counter++;
} Syntax can be discussed, but separating it out into its own block would make it clearer (especially for beginners) as to what is going on. I think it makes it easier to reason about when it is clearer which lines of code are skipped on subsequent calls. This also "fixes" the issue of how to indicate whether you want a static or instance field, as it is now done the same way as before inside the fieldinit block. |
Beta Was this translation helpful? Give feedback.
-
Two alternative approaches worth discussing:
Local Variable Attributes + IL RewritingPermitting Attributes to be used with local variables would allow us to write code as follows, then rewrite the IL to leverage a static via something like Fody. Maybe this is a nontrivial breaking change to IL? This shifts the problem of "when is static initialized", "what if we want to declare instance fields in a function" to the library. class C {
int MyFunction() {
[Static] var x = 10; // Declares static class CMyFunction_statics { static int x = 10; }
return x++; // IL-rewrite to CMyFunction_statics.x++
}
} I guess technically this can already be done via IL Rewriting with something like: __unique_struct
The nice thing is, everything builds on top of existing C# semantics; we're not really introducing any new concepts beyond __unique_struct. static class Statics<TValue, TUnique> {
private static TValue s_Value;
private static bool s_isFirstAccess;
public static ref TValue GetRef(out bool isFirstAccess) {
isFirstAccess = s_isFirstAccess;
s_isFirstAccess = false;
return ref s_Value;
}
}
class C {
int MyFunction() {
ref var x = Statics<int, __unique_struct>.GetRef(out bool isFirstAccess);
if (!isFirstAccess) {
x = 10;
}
// ... additional computation
}
} Alternatively you could use static class Statics<TValue, TUnique> {
private TValue value;
private ?? someSynchronizationState; // use interlocked or mutex to ensure factory() is run once
public static TValue GetOrInit(Func<TValue> factory) { /*...*/ }
}
class C {
int MyFunction() {
var reusedList = Statics<List<int>, __unique_struct>.GetOrInit(() => new());
// ... additional computation
}
} Another approach: static class Statics<TValue, TUnique> {
private TValue value;
public ref TValue Get() => ref value;
}
class C {
int MyFunction() {
var reusedList = Statics<List<int>, __unique_struct>.Get() ??= new();
// /* Or maybe: */ var reusedList = Statics<List<int>>.Get<__unique_struct>() ??= new();
// /* Or maybe: */ var reusedList = Statics<__unique_struct>.Get<List<int>>() ??= new();
// ... additional computation
}
} Likewise, you could have a Of course, with __unique_struct you still run into the question of how it interacts with generic classes and generic methods. My proposal would be to drop any containing generic arguments and hoist __unique_struct to its containing namespace, e.g. |
Beta Was this translation helpful? Give feedback.
-
I wish there's a summery on github that makes it a little bit more clear what people are talking about, so are we gonna do this or not |
Beta Was this translation helpful? Give feedback.
-
I have a question I also see readonly local being champion, if there's so many champion waiting for people to work on it and bring it to the language design meeting why not hire more people to do it given how much money microsoft has, more feature means more competitive on the market, and better dev experience leads to more people making users happy which in turns pay for stuffs |
Beta Was this translation helpful? Give feedback.
-
Sorry, I didn't understand it. How do you know to use this cached value? He has no name. |
Beta Was this translation helpful? Give feedback.
-
Local variable that outlives the local is an awesome concept if not one the the most important one we are getting. |
Beta Was this translation helpful? Give feedback.
-
An alternative pattern without static local is returning a function instead of what your original method is returning.
Assuming static local lives as long as the instance, it's equivalent to
Where MessageReader is a member method |
Beta Was this translation helpful? Give feedback.
-
Just another thing I think of, if we can reference statics defined in local / function scope, we can do it with enums defined in function scope too :
It makes sense to use a enum defined in another function in the same syntax
Is legal Even What a beautiful world |
Beta Was this translation helpful? Give feedback.
-
We shoud change the title to static local, since it is referred to the feature exists in VB called static locals too |
Beta Was this translation helpful? Give feedback.
-
CA1861 has me wishing for static locals again... |
Beta Was this translation helpful? Give feedback.
-
All of the questions raised above are answered in this proposal - static/instance, generics, and thread safety: class MyClass
{
int _x = 5;
int c = 123;
static int _s = 456;
public static void Foo() // static method
{
static int a = 5; // triggers the creation of a private static nested class of MyClass,
// named something like "<generated>Foo_Statics" (using similar logic to anonymous types, etc.)
// same as adding the following to Foo_Statics: static int a = 5;
static var b = 8; // allow "var" syntax to be consistent with other locals; "static int b = 8;" in "Foo_Statics"
static int c = a + b; // allow referencing other static locals
static int d = _s + c; // allow referencing type-scoped static fields
static string e; // this is included in nullability analysis (warning: e is potentially null)
static string? f; // no warning here - f == null by default
static string g = "abc"; // same here - g is initialized, so not null
static readonly Regex rgx = new Regex("..."); // this is very useful - defer initialization to this method being called.
// Same as adding this to aforementioned "Foo_Statics": static readonly rgx = new Regex("...");
// Regular framework handling of thread-safe static init works fine on "Foo_Statics"
++a; // same as: ++Foo_Statics.a;
Console.WriteLine(a);
int bLocal = Interlocked.Increment(ref b); // gotta be thread safe sometimes
Console.WriteLine(bLocal);
var m = rgx.Match("..."); // same as: var m = Foo_Statics.rgx.Match("...");
Baz(static new[] { "a", "b", "c" });
// adds the following to Foo_Statics: static readonly string[] <InlineLocal>ASDASD = new[] { "a", "b", "c" };
// the call becomes: Baz(Foo_Statics.<InlineLocal>ASDASD);
}
public void Foo<T1, T2>() where T1 : IDisposable
{
static int a = 5; // the statics class is generic, copying it's generic signature from Foo<T1, T2>
// same as: private static class <mangled>Foo_Statics<T1, T2> where T1 : IDisposable { static int a = 5; }
static T1? b; // we can use method's generic arguments for these static local fields, which is a great plus
static nongeneric int c = 6;
// creates MyClass_Foo_T1_T2_NonGenericStatics class (below)
}
public void Bar() // instance method
{
static int a = 5; // works identically to Foo: class Bar_Statics { static int a = 1; }
static readonly Regex rgx = new Regex("..."); // this is very useful in instance methods, too
++a; // same as: ++Bar_Statics.a;
Console.WriteLine(a);
var m = rgx.Match("..."); // same as: var m = Bar_Statics.rgx.Match("...");
// I think the below isn't super helpful, but for those who want the benefits of scoping, here goes:
instance int c = 5; // adds a private field to MyClass named something like "<generated>Bar_c";
// field is set to 5 when class is created, as usual.
// Note the introduction of a new keyword (has to be decided if it can be contextual)
// "c" is very similar to "_x"; it's only different in scoping
// it is DIFFERENT than MyClass.c field:
++c; // increment the instance local field
++this.c; // increment the class-scope field declared on top
instance int d; // same as above, but no initialization, defaults to 0
instance string e; // this is included in nullability analysis (warning: e is potentially null)
instance string? f; // no warning here - f == null by default
instance string g = "abc"; // same here - g is initialized, so not null
//instance readonly int h = 1; // I think this doesn't really work; constructor cannot see this field,
// so it can only be assigned a constant; at which point it's no better than a static readonly field (see above)
}
public static int StaticProp
{
static int staticProp;
get => staticProp - 5; set => staticProp = value + 5;
}
public int Prop
{
static int staticField;
instance int prop;
get => prop - 5; set { prop = value + 5; ++staticField; }
}
public static event EventHandler StaticEvent
{
static int staticField; // this can be useful, I suppose
static EventHandler v; // this is pretty useless - unless we allow defining the getter below
add { v += value; ++staticField; } remove { v -= value; --staticField; } private get { return v; }
}
public event EventHandler Event
{
static int staticField; // this can be useful, I suppose
instance EventHandler v; // this is pretty useless - unless we allow defining the getter below
add { v += value; ++staticField; } remove { v -= value; --staticField; } private get { return v; }
}
}
// this class is created due to "nongeneric" keyword inside a generic method
static class MyClass_Foo_T1_T2_NonGenericStatics
{
internal static nongeneric int c = 6;
}
MyClass.Foo(); // outputs "6" and "9" to console
MyClass.Foo(); // outputs "7" and "10" to console
new MyClass().Bar(); // outputs "6" to console
new MyClass().Bar(); // outputs "7" to console |
Beta Was this translation helpful? Give feedback.
-
Hello - I've been directed here from another discussion thread (#8038) eg . Q: When would it be initialized? Q: What happens if that method is called from multiple threads? So in the above example if we did
Hoping someone picks up on this again! |
Beta Was this translation helpful? Give feedback.
-
Originally proposed at dotnet/roslyn#49, as it exists in VB,
Alternatively we could use
static
keyword on the expression that we want to cache,Open question: should this turn to a class-level initializer or it should be initialized lazily as in VB?
Open question: would it make sense to generate a non-static field for a cached value?
Possible translations for the following:
->
plus thread safe variants (note: for value types we should probably use a boolean for lazy initialization.)
Open question: would it worth it to use different keywords to specify a particular translation?
Beta Was this translation helpful? Give feedback.
All reactions