dotnet / csharplang

The official repo for the design of the C# programming language
11.55k stars 1.03k forks source link

Update several properties of a single object #1449

Open JeanCollas opened 6 years ago

JeanCollas commented 6 years ago

I don't find any way to update several properties of a single object in C# (similar to the with VB statement). It may be useful to clarify some mapping functions where several properties has to be updated at once.

I believe a . is a very concise and clear way to handle such case, and that it should not require an additional keyword for this functionality.

Here is a very short example

public class MyLink
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Link { get; set; }
    public MetaData Meta { get; set; }
    public DateTime? Deleted { get; set; }
}
public class MetaData
{
    public DateTime LastUpdate { get; set; }
    public decimal AvgRate { get; set; }
}

public void UpdateLink(MyLink link, string name, string description, DateTime lastUpdate)
{
    link.{
        Name = name,
        Description = description,
        Meta.{
            LastUpdate = lastUpdate
        }
    };
}

Although it might induce some recursive conflicts. The UpdateLink method would be translated by the compiler as:

public void UpdateLink(MyLink link, string name, string description, DateTime lastUpdate)
{
    link.Name = name;
    link.Description = description;
    link.Meta.LastUpdate = lastUpdate;
}

It can be used following factories calls

GetLink(url).{
    Description = description
}

Only a . away from what people is already used to:

new Link( )
{
    Name = name
}
new Link( ).{
    Name = name
}

It can easily handle null values to avoid exceptions:

link?.{
   Name = name
}  
jnm2 commented 6 years ago

See https://github.com/dotnet/csharplang/issues/77 and https://github.com/dotnet/csharplang/issues/803, and the championed https://github.com/dotnet/csharplang/issues/162 (https://github.com/dotnet/csharplang/blob/master/proposals/records.md#with-expressions).

JeanCollas commented 6 years ago

Indeed, not easy to find if there is a relevant issue corresponding :)

However I prefer the use of a '.' than a keyword which does not look like C# syntax. As mentioned in #1434 it can be followed by a question mark '.?' to avoid null exceptions. So far I feel like keywords are more declarative in C#, where '.' which links properties of objects seems more appropriate.

Thanks for the links!

jnm2 commented 6 years ago

@JeanCollas Certainly not easy! No problem.

C# is adding many new keywords such as catch (Exception ex) when (ex.X == 42) and case Foo foo when foo.X == 42 and besides x is null, there are more proposed for pattern matching such as and, or and not. Not to mention LINQ. A with keyword seems consistent with those, doesn't it?

Now whether . could be a better syntax than when: maybe? I'm not fond of foo.{ which feels a little like we're heading toward ascii sneeze, but it's really not that bad and you make good points.

JeanCollas commented 6 years ago

@jnm2

For what I have in mind, that kind of keywords in only filters/test, a bit like linq or SQL syntax, or could be declarative like if(oldVar is Type myVar).

Except for this case, not so many keywords are used for assignment shortcut/value handling, it is most of the time done by operators ( =>, autoproperties {get;set;} = defaultValue, ...) That is the reason why I don't see it as a great improvement.

However I will still be fine of course if this is the final choice of the team, but I am afraid that C# gets too verbose/complicated instead of keeping it straight (like javascript syntax). The shorter is the code, the easiest to read it is, and keep the words for what really needs explanations (how is the method, how is the class, etc).

To focus on the with keyword, I feel it would also be confusing as "does it returns the given object with updated values" or "does it gives a new object, copied from the given object, but also with the new values".

If you take a List<string> for example, I would feel like list.With("A", "B") would give me a new list with merged content, and not the initial list with two new values.

Actually to be more precise, I would not expect a new list with merged content, nor an update of the list, but a VIEW of the original list, with these two additional values, like a new presenter of the object with overridden properties.

JeanCollas commented 6 years ago

From what I see there: https://github.com/DavidArno/SuccincT/issues/44 it is already planned as ' with ' syntax for C#8. So I guess any discussion is now just almost vain :D

Starwer commented 6 years ago

I do prefer the syntax proposed by @JeanCollas ... hope this is not too late to change our mind on the with....

DavidArno commented 6 years ago

@JeanCollas,

Please don't go trusting statements made by random folk on the internet, especially when that random folk is me! 😀

with syntax is currently assigned to the C# 8.0 bucket. But there is no guarantee that it will make it into that release and that release may still be a way off (7.3 is the next release, likely due in the next couple of months). So I'd say that the discussion is potentially far from over.

HaloFour commented 6 years ago

Isn't with about creating a new instance with the same property values except for those explicitly specified in the initializer anyway? It's not the same as trying to use initializer-like syntax to modify the current instance.

There are some other proposals around supporting initializer syntax on expressions to support factory methods. That would probably be very similar to this proposal, sans the dot operator. But the team have also expressed little interest in the various proposals to promote mutation of the current state of an instance so I'm not convinced that such proposals have much in the way of legs at the moment.

JeanCollas commented 6 years ago

@DavidArno I will take this advice seriously :D I am not fan of the with syntax solution, and I hope it will be improved.

@HaloFour It is actually not really clear to me. After more reading, I guess you are right. In that case, I believe this with operator could lead to potential unexpected forbidden states, and shall be used carefully.

Starwer commented 6 years ago

It would be nice if this could work to initializers, and to methods. I think that a WinForm code could eventually look structured with this proposal.

DavidArno commented 6 years ago

@JeanCollas,

I see MS proved my point about trying to guess releases of the language. On the very same day that I suggest C# 7.3 might appear in a month or two, they release C# 7.3! 😆

JeanCollas commented 6 years ago

@Makeman-from-Makeloft Thanks for your message. I am not sure this exactly answers my issue with is more about simplified syntax/code structure, but it is very interesting on patterns. There are several new points I do not master enough yet. The Check/Match pattern looks nice

JeanCollas commented 6 years ago

After a second read of this long post, I only half agree with this. It looks nice, but actually it induces some things similar to global variables / inline delegates that you actually cannot easily extract from the code. The possibilities are nice on a first approach, but a deeper reflection will make me not use them because it will look like quick and dirty solutions :/

jnm2 commented 6 years ago

For those who weren't around earlier: The banning has nothing to do with the workarounds he is suggesting. Personally, like @JeanCollas, we've seen (and tried to discuss) some drawbacks with them, but we are happy for anyone to look at them and use them if they like them. The reason the community would rather ignore Makeman/Makeloft is due to this person's blatant disregard for the code of conduct. This person is responsible for a series of both in-your-face rude comments and incessant spamming on threads that were not related, and he was recalcitrant to appeals. After many community members complained, the admins banned this person. He has since created dozens of sock puppet accounts and continued the spamming. In other words, all the signs say that talking to this person is a waste of time. 😟

FurkanKambay commented 6 years ago

Is there a place for a word like populate in that syntax or can it be modified so? I think it's worth considering. But I like the idea and the syntax.

AustinBryan commented 6 years ago

Isn't this sort've similar to a with keyword in other languages?

public class MyLink
{
    public string Name        { get; set; }
    public string Description { get; set; }
    public string Link        { get; set; }
    public MetaData Meta      { get; set; }

    public void DrawLink() { }
}
...
public void UpdateLink(MyLink link, string name, string description, DateTime lastUpdate)
{
    with link
    {
        Name = name;
        Description = description;

        with Meta 
        {
            LastUpdate = lastUpdate;
        }

        DrawLink();
    }
}

Basically, the with keyword creates a block of code that behaves as if it is a part of that class, allowing you to access the fields and methods directly. It probably shouldn't expose private fields and methods, though.

Regardless of if it uses . or with, might as well allow the usage of methods inside, right?

FurkanKambay commented 6 years ago

@AustinBryan I agree. Also, just a thought: instead of with, how about scope? Like scope (link) { }. Also, I think the brackets are better suited for the language.

JeanCollas commented 6 years ago

@AustinBryan I agree this is similar to with, however having keywords in the middle of the code like this is not much like C#. Except maybe very last updates, there is no keywords inside the methods that modify the scope (using does, but the usage is quite clear). My belief is that it shouldn't be like with or scope, because it induces some interferences if you can add code inside this scope, and how do you manage the following:

    public string Name = "alpha";
    public void T()
    {
        var s=new S() { Name = "beta" };
        with(s)
        {
            Name = "gamma";
        }
        Console.WriteLine(this.Name);
        Console.WriteLine(s.Name);
    }

That is why I think this should not be treated as scope, but only as shortcut

    public void UpdateLink(MyLink link, string name, string description, DateTime lastUpdate)
    {
        link.{
            Name = name,
            Description = description,
            Meta.{
               LastUpdate = lastUpdate
            }
        };
    }

would be translated:

    public void UpdateLink(MyLink link, string name, string description, DateTime lastUpdate)
    {
        link.Name = name;
        link.Description = description;
        link.Meta.LastUpdate = lastUpdate;
    }

which seems quite obvious.

AustinBryan commented 6 years ago

@JeanCollas I agree. with feels way more high level and I really didn't like that language. (GML, had so many issues, like everything being implicitly an int).

So a few questions, would you be able to call methods too, from within the block? I would think so. And would private/protected fields be visible? I'd say no.

What I like about this syntax is that it behaves consistently with if braces. Whenever I see:

if (b) 
{
    // do stuff
}

I think, "just make it simpler and do this:

if (b)
    // do stuff

And in your example, the braces for Meta are unnecessary, I instinctively thought to remove them, which makes:

// braces
Meta.{
    LastUpdate = lastUpdate;
}

// simplified
Meta.LastUpdate = lastUpdate;

Without the braces it's just simply the original syntax, which I really like. If we used with or something, we'd have:

with (Meta) {
    LastUpdate = lastUpdate;
}

// simplified
with (Meta) LastUpdate = lastUpdate;

And that does the exact same thing as the normal way, so there's no point in there being more than one way. That just confuses people.