Open CyrusNajmabadi opened 3 years ago
Instead of saving data, it saves C# code, which is then compiled, and has the code that directly instantiates an object
You have just re-invented binary serialization from first principles. While this design is attractive in its simplicity of use it has several irreconcilable security flaws that have caused many real-world security exploits.
Security aside, what if you want to inter-operate with non-C# code? Being able to easily host a REST api that returns json is one of the cornerstones of why people use ASP.NET or microservices in general. The default design in C# cannot be "other forms of serialization are second class" when they represent 90% of what people are using serialization for.
Correct. It has it's downsides. For my experience, the Scene could also have Custom Code, already. And so there truly was nothing you could accomplish in this sandboxed C# that you couldn't already accomplish via custom C#. So for this scenario, that type of security wasn't an issue.
For MOST situations, you are 100% correct. Which is surely a major reason why it's not used much.
In our case -- to achieve reasonable security, we just compiled the code run-time, so that we could do appropriate checks and confined the compiler environment to not-compile insidious code. And so we serialized the Text, not the DLL directly.
That said -- this makes succinct syntax even more attractive for this scenario.
@najak3d
What that generated C# looks like is a language concern. Is it succinct, or verbose? Does it require extensions to make it work, or does C# syntax allow for the simpler syntax naturally?
No, this is still very much a concern for the library. I understand that you're trying to make this point in the context of your preferred Dart/Flutter way of doing things, but given designers have existed in the .NET ecosystem since inception I think that argument is going to fall flat. Designers don't have a problem in this scenario.
@najak3d
What that generated C# looks like is a language concern. Is it succinct, or verbose? Does it require extensions to make it work, or does C# syntax allow for the simpler syntax naturally?
No, this is still very much a concern for the library. I understand that you're trying to make this point in the context of your preferred Dart/Flutter way of doing things, but given designers have existed in the .NET ecosystem since inception I think that argument is going to fall flat. Designers don't have a problem in this scenario.
More succinct code, so long as it is just as clear - is preferable to more bloated syntax. That's the main point of what I'm saying here. The designer can either generated bloated C# or succinct C# to achieve the same end goal. The succinct notation, is likely superior, so long as it is equally intelligible. In this case, of including .method() calls into the Init Block, it is equally intelligible, and IMO, superior.
It seems a great many things introduced into the C# language have been done despite being able to say "XYZ has existed in the .NET ecosystem for ABC years". The existence of people "dealing with how things are" doesn't mean you therefore don't try to make improvements. Most of the C# changes I've seen qualify as these types of changes; yet they are done anyways, AND were good improvements.
@najak3d
More succinct code, so long as it is just as clear - is preferable to more bloated syntax.
If it's being compiled as a part of the project then it doesn't really matter, as long as it's clear. The WPF designer doesn't have a problem here, and the language isn't going to adopt new syntax for the sake of making life slightly easier for designer code that is likely much easier to emit without trying to construct a complex fluent graph anyway.
Like for Records, and concerns of immutability -- you could achieve this by making constructors that set all of the private fields, and then mark them "readonly". Why do more? Because doing more is helpful and beneficial by making it easier for us to have immutable classes.
@najak3d
More succinct code, so long as it is just as clear - is preferable to more bloated syntax.
If it's being compiled as a part of the project then it doesn't really matter, as long as it's clear. The WPF designer doesn't have a problem here, and the language isn't going to adopt new syntax for the sake of making life slightly easier for designer code that is likely much easier to emit without trying to construct a complex fluent graph anyway.
I do agree that this is a minor concern. Mostly because very little serialization is done in this fashion, and where it is being done, making the code shorter doesn't really help much.
I stepped into this because someone else suggested that "Fluent API might interfere with serialization concerns" -- and my response was simply "if anything, it can only help". My proposal poses zero threat to his concerns over serialization.
The rest was just a brainstorm based on experience... but you are right, that example is not a significant concern for us here.
@najak3d
I stepped into this because someone else suggested that "Fluent API might interfere with serialization concerns" -- and my response was simply "if anything, it can only help". My proposal poses zero threat to his concerns over serialization.
I think you misinterpreted that statement, this has nothing to do with designers. It has to do with how a serialization library would be able to interpret the use of these kinds of methods when it comes to deserializing from a wire format. That is based entirely on reflection and there is no intermediate source in play.
Just confirming, this will work, right?
var x = new Class { StrProp += " suffix" };
I would think so.
Addtionally I would add some scenario that came up today:
I have a read-only structure where properties are dependent from other properties in the same structure. It would be completely sufficient to have everything set in the initialization using a compound initialization with init it is not possible for several reasons, because in the example order might play a role, so I cannot access the Blocksize within the initialization:
var gridSize1 = new CudaGridBlockSize
{
BlocksizeX = 64,
BlocksizeY = 1,
BlocksizeZ = 1,
GridsizeX = ((width - 1) / BlocksizeX / 4) + 1,
GridsizeY = ((height - 1) / BlocksizeY) + 1,
GridsizeZ = ((depth - 1) / BlocksizeZ) + 1
}
so I came up with the idea of using withers but found that also here I have no access to the original source context is lost:
var gridSize1 = new CudaGridBlockSize
{
BlocksizeX = 64,
BlocksizeY = 1,
BlocksizeZ = 1
}
with
{
// Accessing gridsize1 does not work
GridsizeX = ((width - 1) / gridSize1.BlocksizeX / 4) + 1,
GridsizeY = ((height - 1) / gridSize1.BlocksizeY) + 1,
GridsizeZ = ((depth - 1) / gridSize1.BlocksizeZ) + 1
};
The current solution would be:
var gridSize1 = new CudaGridBlockSize
{
BlocksizeX = 64,
BlocksizeY = 1,
BlocksizeZ = 1
} ;
gridSize1 = gridSize1 with
{
GridsizeX = ((width - 1) / gridSize1.BlocksizeX / 4) + 1,
GridsizeY = ((height - 1) / gridSize1.BlocksizeY) + 1,
GridsizeZ = ((depth - 1) / gridSize1.BlocksizeZ) + 1
};
which is not super complex but if you are working on this it might be something that also fits into this topic.
Came here from an 2014 SO thread LOL... Would be really nice to have such feature available, especially for those event callbacks. 😭
/subscribed
This is exactly the feature I've been waiting for. But how about Increment/Decrement operators? These are still useful in some common scenarios. Isn't it in scope?
var countUp = myRecord with { Count++ };
This is exactly the feature I've been waiting for. But how about Increment/Decrement operators? These are still useful in some common scenarios. Isn't it in scope?
var countUp = myRecord with { Count++ };
I think allowing postincrement and postdecrement operators may be confusing, as they usually return the previous state as expression result, and increment the source variable. A postincrement as above could be interpreted as create countUp
with the same count than myRecord
, and then increment myRecord.Count
.
A similar problem for preincrement and predecrement, both records would end up with the new value.
Apart form the fact that, when myRecord
is read only, the property cannot be modified at all.
Just my two cents...
could be interpreted as create
countUp
with the same count thanmyRecord
, and then incrementmyRecord.Count
.
I don't see why you wouldn't read this the same way then.
var countUp = myRecord with { Count += 1 };
They both convey exactly the same mutation on exactly the same storage location. I take it to be an issue of how we intuitively read it, but I think the fact that it's part of a with
expression sufficiently clarifies.
++Count
means the same as Count += 1
, but Count++
has a slight difference:
var count = 0;
Console.WriteLine(count += 1); // 1
Console.WriteLine(++count); // 2
Console.WriteLine(count++); // Still 2
They can mean something slightly different, yes, but not in this situation.
Well... I can kind of see where it depends on how you look at it. But one interpretation makes sense, and one does not. We aren't keeping the value of the "expression", we're keeping the side-effect.
In other words, if you interpret with {count++}
to mean to modify the source, then how could you not interpret with {count += 1}
the same way? They're both modifying the same thing.
Compound assignment in object initializer and
with
expressionSummary
Allow compound assignments like so in an object initializer:
Or a
with
expression:Motivation
It's not uncommon, especially in UI frameworks, to create objects that both have values assigned and need events hooked up as part of initialization. While object initializers addressed the first part with a nice shorthand syntax, the latter still requires additional statements to be made. This makes it impossible to simply create these sorts of objects as a simple declaration expression, negating their use from things like expression-bodied members, switch expressions, as well as just making things more verbose for such a simple concept.
The applies to more than just events though as objects created (esp. based off another object with
with
) may want their initialized values to be relative to a prior or default state.Detailed design - Object initializer
The existing https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#object-initializers will be updated to state:
The spec language will be changed to:
Detailed design -
with
expressionThe existing with expression spec will be updated to state:
The spec language will be changed to:
Design Questions/Notes/Meetings
Note: there is no concern that
new X() { a += b }
has meaning today (for example, as a collection initializer). That's because the spec mandates that a collection initializer's element_initializer is:By requiring that all collection elements are
non_assignment_expression
,a += b
is already disallowed as that is an assignment_expression.--
There is an open question if this is needed. For example, users could support some of these scenarios doing something like so:
That said, this would only work for non-init members, which seems unfortunate.
LDM Discussions
https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-20.md#object-initializer-event-hookup