Recently we released PostSharp.Community.StructuralEquality. If you add the NuGet package to your project and then add the new attribute [StructuralEquality] to your classes, a field-by-field implementation of Equals and GetHashCode will be added to your classes automatically during compilation.

Fody has had this feature in its Equals.Fody add-in for many years and we’re to a large context copying their code and approach, with only some changes that I describe below.

How to use it

After you add the PostSharp.Community.StructuralEquality NuGet package, you can add the [StructuralEquality] attribute to your code like this:

[StructuralEquality]
class Dog
{
    public string Name { get; set; }
    public int Age { get; set; }

    public static bool operator ==(Dog left, Dog right) => Operator.Weave(left, right);
    public static bool operator !=(Dog left, Dog right) => Operator.Weave(left, right);
}

Then you can write code as though the Dog class had Equals, GetHashCode and equality operators implemented. That’s because at build time, after your code compiles, PostSharp adds synthesized overrides of Equals and GetHashCode to the compiled IL code and it replaces the method bodies of equality operators.

The result will be the same as if you used the following code:

class Dog : IEquatable<Dog>
{
    public string Name { get; set; }
    public int Age { get; set; }
    public static bool operator ==(Dog left, Dog right) => object.Equals(left,right);
    public static bool operator !=(Dog left, Dog right) => !object.Equals(left,right);
    
    public override bool Equals(Dog other)
    {
        bool result = false;
        if (!object.ReferenceEquals(null, other))
        {
            if (object.ReferenceEquals(this, other))
            {
                result = true;
            }
            else if (object.Equals(Name, other.Name) && Age == other.Age)
            {
                result = true;
            }
        }
        return result;
    }
    
    public override bool Equals(object other)
    {
        bool result = false;
        if (!object.ReferenceEquals(null, other))
        {
            if (object.ReferenceEquals(this, other))
            {
                result = true;
            }
            else if (this.GetType() == other.GetType())
            {
                result = Equals((Dog)other);
            }
        }
        return result;
    }
    
    public override int GetHashCode()
    {
        int num = Name?.GetHashCode() ?? 0;
        return (num * 397) ^ Age;
    }
}

 

This is the basic no-configuration example. There are ways to customize the synthesized code, for example by excluding some fields or properties. You can see the advanced case in the GitHub repository readme for details.

Field-by-field comparison

The Equals method in the synthesized code compares the fields of each type, not the properties. Backing fields of auto-implemented properties are still compared.

We feel that both field-by-field and property-by-property comparison are reasonable choices for an Equals implementation, but that fields represent the state of the object better. Fields contain the actual data to be compared, whereas properties could do redundant calculations: you could have several properties with different types or values all based on a single field. It would be wasteful to compare them all.

That’s why we decided to implement field-by-field comparison.

Equality operators

You may have noticed that to have StructuralEquality implement equality operators, you still needed to add the following code to your class:

public static bool operator ==(Dog left, Dog right) => Operator.Weave(left, right);
public static bool operator !=(Dog left, Dog right) => Operator.Weave(left, right);

Obviously, it would be nicer if PostSharp could do without even this code and do it all behind the scenes. However, that is not possible:

Suppose you create a class:

class A { } 

And then, in the same project, use == on it:

bool Test(A one, A two) {
  return one == two;
}

Here’s the IL code that gets emitted:

IL_0001: ldarg.1      // one
IL_0002: ldarg.2      // two
IL_0003: ceq

The instruction ceq means “compare the two operands” and, for reference types, it does reference comparison.

But if you replace the Test method with:

bool Test(A one, A two) {
  return object.ReferenceEquals(one, two);
}

Then the IL code stays the same! PostSharp and Fody run only after compilation ends and cannot access the original source code. We could replace all ceq, beq and bne instructions with calls to op_Equality (which is what’s emitted if the operators actually are in the original source code), but we wouldn’t be able to differentiate between whether “==” or “object.ReferenceEquals” was used.

More discussion on this topic can be found at the Equals.Fody GitHub issue #10.

What about generating Equals into source code?

Tools like ReSharper (and Rider) allow you to generate Equals, GetHashCode and equality members into your source code based on selected fields and properties of the class. 

Compared with Fody and StructuralEquality, generating code the way ReSharper does it keeps your build process cleaner and hides less code from you, but it comes with two downsides:

  • When you add a field or property later, it will not be automatically added to your equality members.
  • Your main code file is cluttered with generated code.

Conclusion

With this StructuralEquality package, you can have Equals and GetHashCode methods implemented very quickly. The synthesized code takes into account all fields in the class so it’s updated automatically when you add a new field or auto-property. Finally, the actual code that you see in your IDE remains clear and simple.

For more information on StructuralEquality including more options, see the GitHub readme file.

 

We are happy to announce that PostSharp 6.7 Preview is available today. Included in this release are long-awaited support for Xamarin and .NET 5. The download is available on our website

Xamarin support 

PostSharp has supported .NET Standard for a long time, and that opened the way to bring back the support for Xamarin. From 6.7 release you will be able to use PostSharp in .NET Standard projects that can be then referenced in your Xamarin application project.  The support includes creating custom aspects as well as using PostSharp Pattern Libraries. 

Note that with Xamarin, we still just support .NET Standard libraries, so you cannot use PostSharp on a project that is built for Xamarin. As we write this, we are updating our test suite and running them on physical mobile devices. Starting with 6.7, we will run these tests regularly and will guarantee (when 6.7 will be stable) that .NET Standard assembly, enhanced by PostSharp, will safely work on Xamarin.

.NET 5 support 

We are implementing full support for .NET 5, not limited only to .NET Standard projects. We've fixed several issues that were preventing building with .NET 5.0 Preview 4 SDK and targeting .NET 5.0 framework. We would be happy if you will try to build you own projects using the preview SDK or target .NET 5 in your projects with PostSharp. 

Summary 

PostSharp 6.7 Preview is a release with two main improvements: support for Xamarin and .NET 5. 

Please note that this is a preview release, so do expect minor problems. We are not fully done with testing just yet and we will continue to test our product with more .NET runtimes. Please do get back to us to report any problems. 

Happy PostSharping! 

Last month, we released PostSharp.Community.Packer, a free and open-source tool that you can use to pack your .NET Framework application into a single .exe file for distribution.

The basic idea as well as much of the code itself come from Costura.Fody, an add-in to Fody, which has the same effect. We mostly differ only in the architecture we use to apply our changes to the build process. Like Costura, Packer is released under MIT.

You can find instructions on how to add Packer to your project on the Packer GitHub page.

When I first encountered Costura.Fody, I was amazed. Before I discovered it, I needed to use an installer or complex tools like ILMerge/ILRepack, but Costura.Fody proved that you can have a self-contained .exe by adding a NuGet package, and it just works. So now we have that, too.

Why should I distribute my application as a single .exe file?

When you distribute an application, you want to make it as easy as possible for the end-user to download and start your application. The easier this process is, the more likely the user is to run your application.

Traditionally on Windows, you distribute applications using installers. But installers are heavyweight and represent a barrier themselves: users may not be as willing to install your application, especially if it’s small, but they may be more willing to just run it.

Another common way to distribute an application is as a .zip file that the user needs to extract. But in this case, the user needs to have enough computer skills to know that they must extract rather than run directly from the .zip file (in which case your project would probably silently crash) and they must be able to identify the correct .exe file to run from possibly many files that are in the .zip file. This is a problem especially because the .dll files that you depend on are normally all in the same directory as your main .exe file.

Updating is also harder with a .zip file where the user must remember where they kept your application when they extract the update... and you’d better hope that the possible extra files that are no longer required don’t mess the application up.

Finally, there’s the matter of aesthetics. A single file just looks so much cleaner than a folder with the main .exe file hiding among dozens of libraries!

Distributing your application as single .exe file allows the user to download only a small file and immediately run it. It doesn’t clutter their computer as an installer would, and it doesn’t have the risk that they won’t be able to open it as a .zip file would.

Can’t .NET Core 3 already do this on its own?

That is true. However, this would require you to use .NET Core. 

Since .NET Framework is installed on every Windows machine, you can expect your small single-file .exe program to work everywhere. (If you want to be sure about older versions of Windows, you need to choose an older version of .NET Framework.) This means that your final .exe file can be even 1 MB or 2 MB in size. What is more, our telemetry data shows that, despite the hype .NET Core and .NET 5 are receiving, a majority of developers still target .NET Framework. 

On the other hand, with .NET Core 3, you cannot usually expect the end-user to have .NET Core installed on their machine, and so you must include the runtime with your application, which means an extra 30–70 MB that the user needs to download and that can’t be shared among applications.

How does Packer work?

Packer has two components: one works at build time and one at runtime.

At build time, Packer runs in the build process after the main compilation completes. It reads from the disk all assemblies that you have set as “Copy to output folder” which includes most referenced assemblies outside the .NET Framework itself. It also includes assemblies from NuGet packages. Then it puts the content of those assemblies as resources into your .exe file, optionally compressed.

At runtime, Packer registers itself as an assembly loader so that whenever the .NET Framework runtime fails to load a required assembly, it gives a chance to Packer to load it, and Packer will then uncompress the data in your application’s resources and return that as the required assembly to the runtime.

How can I add Packer to my project?

It is very easy:

  1. Install the NuGet package PostSharp.Community.Packer.
  2. Get a free PostSharp Community license at https://www.postsharp.net/essentials
    (When you compile for the first time, you may be asked to enter the license key.)
  3. Add [assembly: Packer] somewhere in your code.

Then build your project and to test that everything is working, copy your resulting .exe file into a standalone folder and run it.

Personal note

At work, I often develop small tools to make life easier for other developers. I found that even there, among tech-savvy colleagues, distribution makes a big difference. Few colleagues would be willing to build a productivity tool from source, but many more would download and run a single .exe or .jar file (depending on the company’s preferred language). Updates – manual or automatic – get easier as well. 

Tools like PostSharp.Community.Packer truly extend your program’s reach.