20 ms·
C# 10 new feature CallerArgumentExpression
- deleted 5y ago[deleted]
- esarbe 5y agoI'm not sure of this. This seems to be very specific for tooling and for 'meta' stuff (error reporting, logging, ..) I much more prefer Scala's approach here[0]. It's not quite so in-your-face. [0] https://docs.scala-lang.org/scala3/guides/macros/macros.html https://docs.scala-lang.org/scala3/guides/macros/macros.html
- kevingadd 5y agoNice to be able to get good assert messages automatically like you could with the C preprocessor.
- trynumber9 5y agoNothing prevents adding another preprocessor to C# which mimic C's macros. Just no one did that for some reason (I wonder why).
- kevingadd 5y agoBut then people would use the preprocessor :-)
- rektide 5y agoWhen I think of "call by expression" I think of R. Are there other languages that are call by expression? I don't know how this compares to others. But it seems weird to me that you get only the value and only the text expression. There's no AST. You don't get some first-class closure of the elements that make up the expression that you could compute on. You also seem to have to compute the expression ahead of time. Ideally I'd love to have a full AST of the expression, with all the intermediary values filled in. Or perhaps, like, an AST with nothing computed, that I can then compute myself. So I guess, like, the intent here is not call by expression. It's meant to inform, but not to allow the callee to compute.
- whoisthemachine 5y agoDo expression trees [0] line up with what R has? I'm not familiar with R's "call by expression" syntax. [0] https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/ https://docs.microsoft.com/en-us/dotnet/csharp/programming-g...
- jcheng 5y agoIn R, every function argument may be 1) ignored, 2) accessed as an AST, 3) passed to another function, or 4) accessed as a value, or some combination thereof. Note that 1, 2, and 3 don’t result in the expression being evaluated, as function arguments are lazy in R. There’s no way to look at only a function’s signature and determine whether a given argument is going to be evaluated never, once, or multiple times, or conditionally (though the vast majority of the time it’s “once”). On the plus side it does allow for some extremely elegant libraries to be written.
- Guillaume86 5y agoThe expressions you mention are already there (Expression trees, since C#3 IIRC?), it was required for the LINQ features.
- int_19h 5y agoIn C#, you don't have implicit call-by-expression, but explicit is possible like so: void Foo(Expression<Func<int, int>> f) { ... } Foo(x => x + 1); The call syntax is just a regular lambda. But in this case, instead of getting a function, you get the AST for its body. Which can be compiled to an actual function and then invoked if desired, or inspected and used for other purposes. I think having it explicit is a good idea, since it tells the caller that something funky is going on. The corresponding feature in R has to be implicit to allow the rest of the language to be desugared into function calls (in R, even assignments, statement-separating semicolons, and function definitions are handled that way). It's neat conceptually, but now every single function call can do crazy things - and some libraries make use of that.
- jimbob45 5y agoC# continues to quietly release incredible features to little fanfare. I hear the same from the Rust and Zig communities. Really feels like we’re in the golden age of language usability and development. One thing I’d like to see in C# is the theft of the .. operator from Dart so that you can chain calls on a single object without needing to return that object in each call.
- Philip-J-Fry 5y agoIs it incredible features, or is it more compiler magic that infects codebases and requires a literal language glossary to navigate?
- ramenmeal 5y agoYeah I used to love these releases as a c# developer but now I think they're doing more harm than good. I guess it's my time as a go developer that has changed me.
- bsaul 5y agogo development surely affects your taste a lot. This is to me one of the greatest benefit of this language : realizing how much better your code becomes when you spend 90% of your time thinking about the problem and 10% on which language feature you're going to use.
- claytongulick 5y agoI just wish go had better json support. I love the language, but I always end up going back to nodejs for API development because of how ridiculously easy it is to implement json based http APIs.
- kgeist 5y agoI'm curious, what is wrong with json support in Go?
- thom 5y agoI guess this doesn’t quite qualify as Greenspun’s tenth rule, but what a gross thing to have to do to make up for a language’s lack of macros.
- Ciantic 5y agoI don't know do you follow C# these days, but they have source generators now. [1] Only rule is that it can't modify existing code, but add more. This way they won't break the intellisense etc. But you probably want hygienic macros? https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/so...
- jayd16 5y agoWhats so gross about an attribute to get a string populated?
- thom 5y agoIt’s like buying a 3D printer and finding out it can only print out the name of the files you give it. Sure it’s doing something, and if that’s the thing you want then great, but you can’t help but look enviously at your friend’s 3D printer that does whatever they ask it to.
- spaetzleesser 5y agoI really don't understand the dislike of macros. They would be super handy for things like WPF's INotifyPropertyChanged or for a lot of logging and parameter checking. Instead you have to do all kinds of weird tricks with refelection that are way more complex. They are adding all this stuff in small pieces instead of having a comprehensible solution.
- slaymaker1907 5y agoI think the C macro system (and other similar macro systems) really ruined macros for a lot of people. A lot of the complaints about macros go away if you track scope carefully for generated code either by enforcing hygiene or by a more complex namespace system such as with syntax-case in Racket/Scheme.
- emmanueloga_ 5y agoThis is fantastic for testing frameworks! makes it trivial to change error messages from something like: 0 should be 42 to, say... expected the(thing(blah blah)) to return 42, but it was 0*
- mgaunard 5y agoC macros did this in 1973 (48 years ago)
- jayd16 5y agoYeah but now we can do it without having to allow macros.
- mgaunard 5y agoWhy would you disallow a powerful and useful language feature? It's better to be practical than dogmatic.
- spaetzleesser 5y agoBut why? What's wrong with macros? I agree that you can do stupid stuff things with macros but before disabling macros I would get rid of reflection first. With reflection you can do way more stupid things than you can do with macros.
- moron4hire 5y agoNah. Macros allow library developers to do really stupid things that can't be improved on. Reflection lets library users get around the regular stupid things library developers do.
- spaetzleesser 5y ago"Reflection lets library users get around the regular stupid things library developers do." Like marking variables as private?
- moron4hire 5y agoYes, especially if that variable is an IDisposable that the library author also forgot to dispose. Like Microsoft's own System.IO.Ports.SerialPort with its underlying data stream.
- literallyaduck 5y agoGreat now I can change behavior based on how someone calls function. This will be great for companies like Volkswagen skirting emissions tests, or making things work for sonarqube
- thwarted 5y agoSounds like Perl's wantarray function.
- jayd16 5y agoYou could always just grab a StackTrace and inspect it if you wanted to go out of your way to harm yourself and those around you.
- slt2021 5y agoits even easier than that, C# (or MSIL compiled code for that matter) can be decompiled back to C# source with very high accuracy.
- ridiculous_fish 5y agoIn this: [CallerArgumentExpression("condition")] string expression = "" what is the role of the `= ""`? I think the answer is given by: If null or a string that is not a parameter name (e.g."notAParameterName") is provided, the compiler will pass in an empty string so if you mistype the parameter name in the decoration, there's no warning, it just gives you an empty string? Why allow that?
- gregoryl 5y agoIt makes it an optional parameter, which means you don't need to pass it. Otherwise every call would need to send an empty string or similar.
- ridiculous_fish 5y agoBut this parameter is supposed to be auto-populated with the text from another parameter.
- deleted 5y ago[deleted]
- wvenable 5y agoIt's only populated if the parameter isn't provided explicitly.
- kevingadd 5y agoYou can pass it manually, and you can invoke this method from other compilers that don't implement the attribute. In those cases you want an explicit parameter and one with a default value.
- ridiculous_fish 5y agoThis explanation makes sense to me. If you invoke this function with reflection, there's no hope of capturing the textual argument from other parameters.
- ComputerGuru 5y agoThis is awesome for dealing with razor bindings in cshtml when you need to manually set or clear an model state validation error. I previously wrote code to reverse compile a lambda specifying the element to set/clear and attempted to reverse that into its most likely form - it was a very disheartening process!
- petilon 5y agoHas anyone seen the recent edition of Stroustrup's The C++ Programming Language book? It is 1376 pages long. This is where C# is headed as well.
- pjmlp 5y agoC# was already bigger than that on C# 1.0, because when people complain about C++ they keep forgeting the standard library is part of those pages.
- tialaramex 5y agoBy a Fourth Edition Stroustrup's book devotes 400 pages of almost 1400 to the C++ Standard Library. These pages are a mix of mindless documentation you'd expect from a reference (which even in 2013 seems pretty useless as an actual book) and Stroustrup's own observations, such as on vectors: > I stopped trying to improve performance using reserve(). Instead, I use it to increase predictability of reallocation delays and to prevent invalidation of pointers and iterators. Here Stroustrup is talking about abusing vector's reserve() to assure yourself that the memory won't get re-allocated and so it's OK to point into the vector directly. Now, who wants to put their hand up to say what a good idea this is? And how about who has tried to maintain any code that was written by people who thought it was a good idea? The book would be better (not good, but better) without this "advice". The Second Edition of Stroustrup's book doesn't explain the Standard Library at all (there was in some sense no "standard library" because ISO isn't done standardising C++ when it's written) and was 700 pages in the edition I own. It's also far from a comprehensive explanation of C++ despite being so thick.
- pjmlp 5y agoAny version of .NET will top those pages, in fact it requires multiple books to discuss the whole stack.
- ripley12 5y agoSeems like a handy feature for things like test frameworks. There's more detail in the official C# docs: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/caller-argument-expression https://docs.microsoft.com/en-us/dotnet/csharp/language-refe... And in the GitHub issue for the feature: https://github.com/dotnet/csharplang/issues/287 https://github.com/dotnet/csharplang/issues/287
- jmull 5y agoI’m not sure about this. Is there a good use case outside of automatic more decent assert messages? That’s nice, but… (1) that’s far from a big problem in writing/maintaining unit tests. Isn’t the first thing you do with a failing unit test is examine the code on the call stack anyway? So this maybe saves you a click, assuming some pretty common tooling? (2) I think you actually want to capture call stack source lines? Not to mention, quite often the assert expression isn’t that interesting. e.g., var result = ComputeAnswer(“what is the meaning of life?”); Assert.AreEqual(42, result); The auto generated failure message would be something like: “result is expected to be 42 but was 41” Whereas what you want is like, “ComputeAnswer(“what is the meaning of life?”) is expected to be 42 but was 41” The style of separating the call to the code under test from the assert is pretty common, often recommended, and clean.
- moron4hire 5y agoMaybe if you gave your variables more meaningful names, like "meaningOfLife" instead of just "result", it would be more useful.
- jmull 5y agoIn that code snippet, “answer” would really be the right name for the variable (because the method is called ComputeAnswer). Redundantly restating immediately apparent context isn’t a good style.
- moron4hire 5y agoI cannot disagree with you any harder. Redundantly naming variables is good style, as it gives you more room to rename functions slightly, split functions, introduce new variables and inject new operations in the middle. And frequently, the function name has nothing to do with the semantics of the returned value. "result := sort(someOtherResult)" doesn't tell me anything. "sortedItems := sort(items)" tells me a lot. result1 := getUserInput() result2 := validate(result1) result3 := parse(result2) result4 := getRouting(result3) or addressInsecureStr := getUserInput() addressStr := validate(ipAddressInsecureStr) address := parse(ipAddressStr) pathAddresses := getRouting(ipAddress) I know which I would choose
- Someone1234 5y agoI like the feature, don't love the boilerplate. If C#/.Net was invented today you could just implicitly have access to this information from within your function's scope. The compiler is smart enough to figure out if the CallerArgumentExpression() is used and optimize away the ones which were not. nameof was added in C# 6, this like that, could have been added too e.g.: $"arg: {nameof(thing)}, with value: {thing}, from caller's: {callerargumentexpression(thing)}". It is a very useful logging/diagnostic tool, but I'd have to throw CallerArgumentExpression("") all over my code just-in-case, and I hate that. PS - Yes I read the whole article, and understand how useful it will be for asserts/specific tooling; my point is that their imagination was too small and this will creep into all kinds of code.
- a_lost_needle 5y agoHaving led the creation of a language/compiler professionally, the number of features that are "nice to have" are nearly infinite. And occasionally a lower priority item that was easier to implement at the time, become far larger in scope because of how another higher priority feature was implemented. Some of these can get scrapped or pushed back till the cycles allow it. Often though, we learn in the implementation, and some of the lower priorities are no longer needed. And Reflections provided a lot of the functionality that you could make seamless from a parent class and attributes.
- wvenable 5y ago> I like the feature, don't love the boilerplate. It's not for you, though. The boilerplate is for a very small number of libraries and frameworks that have use for this feature. The ergonomics of using the resulting methods from these libraries and frameworks are quite good. This is just like the CallerMemberName attribute.
- Someone1234 5y ago> It's not for you, though. It is "not for me" though because of the reason I said: Too much boilerplate for common usage. If it had less it would be for "me" and everyone else too, just like the nameof() is. So it is a niche tool exactly because of the implementation. I bet a lot of people expected nameof() to be niche too, but it isn't.
- deleted 5y ago[deleted]
- malkia 5y agoI don't think it works with "params" - https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/params https://docs.microsoft.com/en-us/dotnet/csharp/language-refe...
- ziml77 5y agoYes, it would be just like the attributes that already exist for getting the caller method name, line, and file. It's unfortunate, but it would be a bigger language change to fix that.
- chem83 5y agoThe guard clauses remind me of the compile-time verification done by Microsoft's Spec#[0] compiler, which included non-null types, checked exceptions and throws clauses, method contracts and object invariants. Some of those saw the light of day with C#, like the non-nulls, but whatever happened to the rest? [0] http://www.rosemarymonahan.com/specsharp/papers/ECOOPTutorial.pdf http://www.rosemarymonahan.com/specsharp/papers/ECOOPTutoria...
- int_19h 5y agoIsn't that what evolved into System.Diagnostics.Contracts eventually?
- jve 5y agoOnce I tried to use them, but forgot what was the issue... first of all - couldn't easily google on how to use it and then I think some legacy stuff was there that couldn't be used in .NET 4.7 > Code contracts aren't supported in .NET 5+ (including .NET Core versions). Consider using Nullable reference types instead. https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts https://docs.microsoft.com/en-us/dotnet/framework/debug-trac... I didn't work any .NET Core version, but... could someone maybe provide some material/blogs/examples on how Contracts are to be used? Statically verifiable arguments (where possible) are a dream that hasn't yet come true for me...
- int_19h 5y agoYep, it has been effectively deprecated for a while, and now gone in new .NET versions. But when it was there, there was a static checker that you could run, and I thought that's where all the Spec# bits ended up in. .NET 5+ basically doesn't have anything like contracts out of the box anymore.
- polskibus 5y agoThat's a veery long name for such a decorative feature. I'd preferred something less verbose - this is just bloat. The nameof() is not that bad and does not bloat function signature.
- deleted 5y ago[deleted]
- ygra 5y ago1. You're probably not using this on every other method, and the name is consistent with other features in the same vein (CallerMemberNameAttribute, etc.). 2. Something like nameof wouldn't really work here as the idea is to re-use both evaluation of an expression _and_ the textual form. nameof doesn't evaluate its argument. So a similar solution would require you to write your expression twice.
- LadyCailin 5y ago`expressionfor(argument)` for instance doesn’t require modifying the signature manually, and should work similarly to nameof(argument).