The proven MSIL-based
aspect-oriented framework for C#
to reduce boilerplate and architecture erosion.
What are boilerplate code and architecture erosion costing you?
A Total Waste of Your Time
Manually writing boilerplate is not only boring and frustrating, it is a total waste of your time. Why do manually what a machine can do better and faster?
Although allowing architecture to erode initially saves time, but as it progressively accumulates, it makes everybody on the team less productive.
Excessive Complexity
Your business logic is unreadable because it is littered with boilerplate and the source code becomes so complex that your brain can no longer make sense of it.
As source code derives from the original architecture, more and more exceptions to the rule are committed, and the conceptual complexity of the source code increases.
Avoidable Defects
Boilerplate is littered with bugs because it's created by copy-paste programming and mostly remains untested. Production-readiness features such as logging or caching are neglected because they are too expensive to build without the proper tooling.
As your codebase complexity increases its overall quality suffers: end-users experience bugs and performance degrades.
Expensive Maintenance
With both boilerplate and architecture erosion increasing complexity, it becomes more and more expensive to make small changes in your application. A bigger refactoring becomes completely impossible, and you need to rewrite your whole codebase from scratch.
Smart use of meta-programming could extend the lifetime of your codebase by several years.
Aspects:
our core technology
Aspects are special C# classes execute inside the compiler or the IDE and can:
- enhance your source code at compile-time or in the IDE,
- validate your source code and emit errors or warnings,
- suggest code fixes and refactorings.
How does it work?
1. Add the PostSharp package to your project and annotate code with [CustomAttributes].
2. The C# or VB compiler builds your code into binaries.
3. PostSharp analyzes the binary and injects the implementation of the aspects.
4. Your app, production-ready
At run time, both your business logic and the aspects are seamlessly executed. Your source code remains crystal clear.
The result: your code is
shorter and cleaner.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
public class Address : INotifyPropertyChanged { private string _line1; private string _line2; private string _city; private string _remark; public string Line1 { get => _line1; set { _line1 = value; OnPropertyChanged(); OnPropertyChanged(nameof(FullAddress)); } } public string Line2 { get => _line2; set { _line2 = value; OnPropertyChanged(); OnPropertyChanged(nameof(FullAddress)); } } public string City { get => _city; set { _city = value; OnPropertyChanged(); OnPropertyChanged(nameof(FullAddress)); } } public string Remark { get => _remark; set { _remark = value; OnPropertyChanged(); } } public string FullAddress => string.Join(Environment.NewLine, Line1, Line2, City); public event PropertyChangedEventHandler PropertyChanged; // TODO: Weak event. protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class Person : INotifyPropertyChanged { private string _firstName; private string _lastName; private Address _address; public string FirstName { get => _firstName; set { _firstName = value; OnPropertyChanged(); } } public string LastName { get => _lastName; set { _lastName = value; OnPropertyChanged(); } } public Address Address { get => _address; set { _address = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; // TODO: Weak event. protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class PersonViewModel : INotifyPropertyChanged { private Person _person; private bool _includeAddressInDetails; // TODO: Weak reference (if weak events are implemented). private Address _observedAddress; public Person Person { get => _person; set { if (_person != null) { _person.PropertyChanged -= OnPersonPropertyChanged; if (_observedAddress != null) { _observedAddress.PropertyChanged -= OnAddressPropertyChanged; _observedAddress = null; } } _person = value; if (value != null) { value.PropertyChanged += OnPersonPropertyChanged; _observedAddress = value.Address; if (_observedAddress != null) { _observedAddress.PropertyChanged += OnAddressPropertyChanged; } } OnPropertyChanged(); OnPropertyChanged(nameof(FullDetails)); } } private void OnAddressPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(Address.FullAddress)) { OnPropertyChanged(nameof(FullDetails)); } } private void OnPersonPropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(Person.FirstName): case nameof(Person.LastName): OnPropertyChanged(nameof(FullDetails)); break; case nameof(Person.Address): if (_observedAddress != null) { _observedAddress.PropertyChanged -= OnAddressPropertyChanged; _observedAddress = null; } _observedAddress = _person.Address; if (_observedAddress != null) { _observedAddress.PropertyChanged += OnAddressPropertyChanged; } break; } } public bool IncludeAddressInDetails { get => _includeAddressInDetails; set { _includeAddressInDetails = value; OnPropertyChanged(); OnPropertyChanged(nameof(FullDetails)); } } public string FullDetails { get { // We don't handle the nulls for simplicity of the argument. if (IncludeAddressInDetails) { return string.Join(this.Person.FirstName + " " + this.Person.LastName, this.Person.Address.FullAddress); } else { return this.Person.FirstName + " " + this.Person.LastName; } } } public event PropertyChangedEventHandler PropertyChanged; // TODO: Weak events. [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[NotifyPropertyChanged] public class Address { public string Line1 { get; set; } public string Line2 { get; set; } public string City { get; set; } public string Remark { get; set; } public string FullAddress => string.Join(Environment.NewLine, Line1, Line2, City); } [NotifyPropertyChanged] public class Person { public string FirstName { get; set; } public string LastName { get; set; } public Address Address { get; set; } } [NotifyPropertyChanged] public class PersonViewModel { public Person Person { get; set; } public bool IncludeAddressInDetails { get; set; } public string FullDetails { get { // We don't handle the nulls for simplicity of the argument. if (IncludeAddressInDetails) { return string.Join(this.Person.FirstName + " " + this.Person.LastName, this.Person.Address.FullAddress); } else { return this.Person.FirstName + " " + this.Person.LastName; } } } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public partial class MainWindow : Window { private MainViewModel _mainViewModel = new MainViewModel(); public MainWindow() { InitializeComponent(); this.OpenFile = new OpenFileCommand(_mainViewModel); this.CloseFile = new CloseFileCommand(_mainViewModel); } public ICommand OpenFile { get; } public ICommand CloseFile { get; } class OpenFileCommand : ICommand { private MainViewModel _mainViewModel; public OpenFileCommand(MainViewModel mainViewModel) { _mainViewModel = mainViewModel; } public bool CanExecute(object parameter) => true; public void Execute(object parameter) => _mainViewModel.OpenFile(); public event EventHandler CanExecuteChanged; } class CloseFileCommand : ICommand { private MainViewModel _mainViewModel; public CloseFileCommand(MainViewModel mainViewModel) { _mainViewModel = mainViewModel; _mainViewModel.PropertyChanged += delegate (object o, PropertyChangedEventArgs args) { if (args.PropertyName == nameof(MainViewModel.IsFileOpen)) { this.CanExecuteChanged?.Invoke(this, EventArgs.Empty); } }; } public bool CanExecute(object parameter) => _mainViewModel.IsFileOpen; public void Execute(object parameter) => _mainViewModel.CloseFile(); public event EventHandler CanExecuteChanged; } } public partial class FancyTextBlock : UserControl { public static DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof(string), typeof(FancyTextBlock), null, value => !string.IsNullOrEmpty(value as string)); public string Text { get => (string)this.GetValue(TextProperty); set => this.SetValue(TextProperty, value); } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public partial class MainWindow : Window { private MainViewModel _mainViewModel = new MainViewModel(); public MainWindow() { InitializeComponent(); } [Command] public ICommand OpenFile { get; private set; } private void ExecuteOpenFile() => _mainViewModel.OpenFile(); [Command] public ICommand CloseFile { get; private set; } private void ExecuteCloseFile() => _mainViewModel.CloseFile(); public bool CanExecuteCloseFile => _mainViewModel.IsFileOpen; } public partial class FancyTextBlock : UserControl { [DependencyProperty, Required] public string Text { get; set; } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class Icecream : IFreezable { private double _temperature; public FreezableList<Ingredient> Ingredients { get; } = new FreezableList<Ingredient>(); public double Temperature { get => _temperature; set { if (this.IsFrozen) throw new ObjectReadOnlyException(); _temperature = value; } // Hello, world. } public void Freeze() { this.IsFrozen = true; this.Ingredients.Freeze(); } public bool IsFrozen { get; private set; } } public class Ingredient : IFreezable { private string _name; private double _quantity; public string Name { get => _name; set { if (this.IsFrozen) throw new ObjectReadOnlyException(); _name = value; } } public double Quantity { get => _quantity; set { if (this.IsFrozen) throw new ObjectReadOnlyException(); _quantity = value; } } public void Freeze() { this.IsFrozen = true; } public bool IsFrozen { get; private set; } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Freezable] public class Icecream { public FreezableList<Ingredient> Ingredients { get; } = new FreezableList<Ingredient>(); public double Temperature { get; set; } } [Freezable] public class Ingredient { public string Name { get; set; } public double Quantity { get; set; } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class UserNameService { private Logger logger; private Db db; // It's just one method, but you should do this for thousands. public string GetUserName(int userId) { var methodLogger = logger.Start("UserNameService.GetUserName({userId})"); try { string userName = db.Context.Users.Single(u => u.Id == userId).Name; methodLogger.Success(userName); return userName; } catch (Exception e) { methodLogger.Exception(e); throw; } } }
2
3
4
5
6
7
8
9
10
11
public class UserNameService { private Db db; // Logging is generally configured in an xml config file. public string GetUserName(int userId) { return db.Context.Users.Single(u => u.Id == userId).Name; } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public interface IBananaFactory { Banana GetBanana(string kind, double length, double diameter); } public class BananaFactory : IBananaFactory { public Banana GetBanana(string kind, double length, double diameter) { if (string.IsNullOrEmpty(kind)) throw new ArgumentNullException(nameof(kind)); if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); // Yep copy-pase error again. if (length < 0 || diameter > 5) throw new ArgumentOutOfRangeException(nameof(length)); return new Banana(kind, length, diameter); } } public class Banana { private string _kind; private double _length; private double _diameter; public Banana(string kind, double length, double diameter) { Kind = kind; Length = length; Diameter = diameter; } public string Kind { get => _kind; set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); _kind = value; } } public double Length { get => _length; set { if (value < 0) throw new ArgumentOutOfRangeException(nameof(value)); _length = value; } } public double Diameter { get => _diameter; set { if (value < 0 || value > 5) throw new ArgumentOutOfRangeException(nameof(value)); _diameter = value; } } }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public interface IBananaFactory { Banana GetBanana([Required] string kind, [Positive] double length, [Range(0, 5)] double diameter); } public class BananaFactory : IBananaFactory { public Banana GetBanana(string kind, double length, double diameter) { return new Banana(kind, length, diameter); } } public class Banana { public Banana(string kind, double length, double diameter) { Kind = kind; Length = length; Diameter = diameter; } [Required] public string Kind { get; set; } [Positive] public double Length { get; set; } [Range(0, 5)] public double Diameter { get; set; } } n class="codePunctuation">}

Get started for FREE
with PostSharp Essentials

Our complete toolbelt:
Success stories

Siemens Audiology
Siemens Audiology saved 15% development time plus improved code readability by removing unwanted boilerplate code.
When the team at Siemens Audiology was tasked with building a new WPF implementation for two of its leading hearing system software applications, a big challenge was to find a way to save coding time for developers implementing ViewModels while increasing code readability.

ATS Global
ATS Global saves 16% lines of code.
When the team at ATS Global needed to build a complex shop floor simulation, they faced the challenge of potential multithreading issues and the complexity of writing synchronization code.
The team turned to PostSharp and was able to write thread-safe code without training all team members in advanced multithreading and delivered required features with 16% fewer lines of code.

Thales Information Systems
Thales is more focused on business logic.
When the team at Thales Information Systems was tasked with a big refactoring on a project, one of the challenges was to simplify architecture by reducing boilerplate code. The architect chose PostSharp to implement custom aspects to handle logging, performance counters and INotifyPropertyChanged and keep junior team members better focused on business logic.

Gamesys
Gamesys improves productivity with PostSharp.
Gamesys serves around one million daily active users across its social games. Their backend services handle more than 250 million requests per day. Despite its massive scale, this distinctive service is being maintained by a remarkably small development team of just seven super-productive individuals.

Cognitive X Solutions
PostSharp took care of 95% of CognitiveX's INPC code.
When Cognitive X was looking for ways to deliver value to their clients while differentiating themselves from the competition, the team chose PostSharp as one of the core pieces of their strategy. By decreasing the actual lines of code they were writing, as well as helping them enforce best practices, PostSharp allowed them to produce new features for their clients faster, at lower cost, and with fewer errors.