3

If I have a POCO such as this:

[Poco]
public class MyPoco
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
}

... and it's initialised like this:

new MyPoco { FirstName = "Joe", LastName="Bloggs", Address="123 Abc St." }

Can I, using CQL in NDepend, ensure that the properties are only ever set using the object initialiser as above, and not separately.

I'm trying to catch code like this:

myPoco.FirstName="John";
doSomething(myPoco);

... I don't want people to set properties on an existing POCO. I understand I can make the setters private, but I don't like passing in all the properties in a constructor because you end up with a signature such as MyPoco(string, string, string) - making it easier to misalign the parameters (easier than using the object initialiser syntax)

So, I want to catch this using NDepend. Here's the start of my CQL query:

from f in JustMyCode.Fields.Where(f=>
   f.ParentType.HasAttribute ("JetBrains.Annotations.PocoAttribute".AllowNoMatch())) 
       where f.MethodsAssigningMe [I'M STUMPED HERE]

It looks like I can analyse the method setting the property of a POCO, but I need to go one step lower and examing the code block to see if it's in an object initialiser block.

Is this possible?

Steve Dunn
  • 21,044
  • 11
  • 62
  • 87
  • 2
    In C# next this will be a "thing" (primary constructors). In C# current, you'd need to write explicit properties to do this. – Marc Gravell Jun 20 '14 at 08:30
  • What about named parameters. You can set the setters to private and use var *myPoco = new MyPoco(firstName: "", lastName: "", address: "");* – Jernej Gorički Jun 20 '14 at 08:30
  • @MarcGravell: The primary constructors won't help in terms of the calling code though, will it? You won't be able to use object initializer syntax to create an instance, as far as I'm aware. – Jon Skeet Jun 20 '14 at 08:32
  • @JonSkeet indeed, it'll be constructor similar to Jernej's comment above - but the point I was not making very well is that it is a simple way of creating a proper immutable type without all the cruft that you need today – Marc Gravell Jun 20 '14 at 08:33
  • 1
    @MarcGravell: Agreed, and I'm very happy about that - but I don't think it's particularly relevant to the question in this case. (The OP's stated objection to putting things in the constructor isn't the ugliness of having to write manual properties and separate readonly variables.) – Jon Skeet Jun 20 '14 at 08:34
  • @Jon yeah, but IMO the "making it easier to misalign the parameters" is based on a false premise, avoidable by naming the parameters in the call – Marc Gravell Jun 20 '14 at 08:37
  • 1
    With NDepend you can write a rule to ensure that if a method uses a setter of a POCO class, it uses all setters of the POCO class. But you cannot enforce that all these setters are called in an object initialiser. – Patrick from NDepend team Jun 21 '14 at 11:53

1 Answers1

5

I don't know whether you can do this in CQL, although even if you can't, you might well be able to do it with Roslyn.

However, you might want to consider using a builder pattern instead:

var poco = new MyPoco.Builder { FirstName = "Joe",
                                LastName="Bloggs",
                                Address="123 Abc St." }.Build();

Now the poco itself can be immutable, but the builder can have the properties set wherever you like.

Heck, if you're a fan of implicit conversions you could even have an implicit conversion from the builder to the poco:

MyPoco poco = new MyPoco.Builder { FirstName = "Joe",
                                   LastName="Bloggs",
                                   Address="123 Abc St." };

While I'm all for tools verifying proper coding where it's not possible to design a type to avoid it being misused to start with, I'd prefer to have an idiot-proof API to start with :)

Also note that with named arguments in C# 4, even the constructor version can be made clear:

var poco = new MyPoco(firstName: "Joe",
                      lastName: "Bloggs",
                      address: "123 Abc St.");

but of course that still allows people to write it without the named arguments...

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194