2
// Gendarme.Rules.Performance.ReviewLinqMethodRule
5
// Jesse Jones <jesjones@mindspring.com>
7
// Copyright (C) 2009 Jesse Jones
9
// Permission is hereby granted, free of charge, to any person obtaining
10
// a copy of this software and associated documentation files (the
11
// "Software"), to deal in the Software without restriction, including
12
// without limitation the rights to use, copy, modify, merge, publish,
13
// distribute, sublicense, and/or sell copies of the Software, and to
14
// permit persons to whom the Software is furnished to do so, subject to
15
// the following conditions:
17
// The above copyright notice and this permission notice shall be
18
// included in all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
using System.Collections.Generic;
35
using Gendarme.Framework;
36
using Gendarme.Framework.Engines;
37
using Gendarme.Framework.Helpers;
38
using Gendarme.Framework.Rocks;
40
namespace Gendarme.Rules.Performance {
43
/// Linq extension methods operate on sequences of values so they generally
44
/// have linear time complexity. However you may be able to achieve better
45
/// than linear time performance if you use a less general method or take
46
/// advantage of a method provided by an <c>Sytem.Collections.Generic.IEnumerable<T></c>
52
/// public string FirstOrMissing (IEnumerable<string> sequence, string missing)
54
/// // Count () is O(n)
55
/// if (sequence.Count () > 0) {
56
/// return sequence.First ();
61
/// public void Append (List<string> lines, string line)
63
/// // Last () is O(n)
64
/// if (lines.Count == 0 || lines.Last () != line) {
73
/// public string FirstOrMissing (IEnumerable<string> sequence, string missing)
75
/// // We don't need an exact count so we can use the O(1) Any () method.
76
/// if (sequence.Any ()) {
77
/// return sequence.First ();
82
/// public void Append (List<string> lines, string line)
84
/// // Lines is a List so we can use the O(1) subscript operator instead of
85
/// // the Last () method.
86
/// if (lines.Count == 0 || lines [lines.Count - 1] != line) {
92
/// <remarks>This rule is available since Gendarme 2.6</remarks>
94
[Problem ("A linq extension method with linear time complexity is used, but a more efficient method is available.")]
95
[Solution ("Use the more efficient method.")]
96
[EngineDependency (typeof (OpCodeEngine))]
97
public sealed class ReviewLinqMethodRule : Rule, IMethodRule {
99
private const string EnumerableName = "System.Linq.Enumerable";
100
private readonly OpCodeBitmask Comparisons = new OpCodeBitmask (0x0, 0x0, 0x0, 0xB);
102
public readonly MethodSignature CountProperty = new MethodSignature ("get_Count", null, new string [0]);
103
public readonly MethodSignature LengthProperty = new MethodSignature ("get_Length", null, new string [0]);
104
public readonly MethodSignature Subscript = new MethodSignature ("get_Item", null, new string [] {"System.Int32"});
105
public readonly MethodSignature Sort = new MethodSignature ("Sort", null, new string [1]);
107
private bool HasMethod (TypeReference type, MethodSignature method)
109
if (type.HasMethod (method))
112
TypeDefinition td = type.Resolve ();
113
return td != null && td.BaseType != null && HasMethod (td.BaseType, method);
116
private void CheckForCountProperty (TypeDefinition type, MethodDefinition method, Instruction ins)
118
if (HasMethod (type, CountProperty)) {
119
string message = "Use the Count property instead of the Count () method.";
120
Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
121
Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
123
} else if (HasMethod (type, LengthProperty)) {
124
string message = "Use the Length property instead of the Count () method.";
125
Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
126
Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
130
private void CheckForAny (TypeDefinition type, MethodDefinition method, Instruction ins)
132
// call System.Int32 System.Linq.Enumerable::Count<System.String>(System.Collections.Generic.IEnumerable`1<!!0>)
135
Instruction n1 = ins.Next;
136
Instruction n2 = n1 !=null ? n1.Next : null;
137
if (n1 != null && n2 != null) {
138
object rhs = n1.GetOperand (method);
139
if (rhs != null && rhs.Equals (0)) {
140
if (Comparisons.Get (n2.OpCode.Code)) {
141
string message = "Use Any () instead of Count ().";
142
Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
143
Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
150
// call System.Int32 System.Linq.Enumerable::Count<System.String>(System.Collections.Generic.IEnumerable`1<!!0>)
152
Instruction p1 = ins.Previous;
153
Instruction p2 = p1 != null ? p1.Previous : null;
154
if (p1 != null && p2 != null && n1 != null) {
155
object rhs = p2.GetOperand (method);
156
if (rhs != null && rhs.Equals (0)) {
157
if (Comparisons.Get (n1.OpCode.Code)) {
158
string message = "Use Any () instead of Count ().";
159
Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
160
Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
166
private void CheckForSubscript (TypeReference type, MethodDefinition method, Instruction ins, string name)
168
if (type.IsArray ()) {
169
string message = string.Format ("Use operator [] instead of the {0} method.", name);
170
Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
171
Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
174
TypeDefinition td = type.Resolve (); // resolve of an array returns the element type...
175
if (td != null && HasMethod (td, Subscript)) {
176
string message = string.Format ("Use operator [] instead of the {0} method.", name);
177
Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
178
Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
183
private void CheckForSort (TypeReference type, MethodDefinition method, Instruction ins, string name)
185
if (type.IsArray ()) {
186
string message = string.Format ("Use Array.Sort instead of the {0} method.", name);
187
Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
188
Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
191
TypeDefinition td = type.Resolve (); // resolve of an array returns the element type...
192
if (td != null && HasMethod (td, Sort)) {
193
string message = string.Format ("Use Sort instead of the {0} method.", name);
194
Log.WriteLine (this, "{0:X4} {1}", ins.Offset, message);
195
Runner.Report (method, ins, Severity.Medium, Confidence.High, message);
200
public override void Initialize (IRunner runner)
202
base.Initialize (runner);
204
// The IEnumerable<T> extension methods were introduced in .NET 3.5
205
// so disable the rule if the assembly is targeting something earlier.
206
Runner.AnalyzeAssembly += (object o, RunnerEventArgs e) => {
207
Active = e.CurrentAssembly.Runtime > TargetRuntime.NET_2_0;
211
public RuleResult CheckMethod (MethodDefinition method)
214
return RuleResult.DoesNotApply;
216
if (!OpCodeBitmask.Calls.Intersect (OpCodeEngine.GetBitmask (method)))
217
return RuleResult.DoesNotApply;
219
Log.WriteLine (this, "--------------------------------------");
220
Log.WriteLine (this, method);
222
// Loop through each instruction,
223
foreach (Instruction ins in method.Body.Instructions) {
225
// if we're calling a method,
226
if (OpCodeBitmask.Calls.Get (ins.OpCode.Code)) {
228
// and the method is a System.Linq.Enumerable method then,
229
var target = ins.Operand as MethodReference;
230
if (target != null && target.DeclaringType.FullName == EnumerableName) {
232
// see if we can use a more efficient method.
233
if (target.Name == "Count" && target.Parameters.Count == 1) {
234
TypeReference tr = ins.Previous.GetOperandType (method);
235
TypeDefinition td = tr.Resolve ();
238
CheckForCountProperty (td, method, ins);
239
CheckForAny (td, method, ins);
242
} else if ((target.Name == "ElementAt" || target.Name == "ElementAtOrDefault") && target.Parameters.Count == 2) {
243
Instruction arg = ins.TraceBack (method);
244
TypeReference tr = arg.GetOperandType (method);
245
CheckForSubscript (tr, method, ins, target.Name);
247
} else if ((target.Name == "Last" || target.Name == "LastOrDefault") && target.Parameters.Count == 1) {
248
TypeReference tr = ins.Previous.GetOperandType (method);
249
CheckForSubscript (tr, method, ins, target.Name);
251
} else if (target.Name == "OrderBy" || target.Name == "OrderByDescending") {
252
Instruction arg = ins.TraceBack (method);
253
TypeReference tr = arg.GetOperandType (method);
254
CheckForSort (tr, method, ins, target.Name);
260
return Runner.CurrentRuleResult;
264
private void Bitmask ()
266
OpCodeBitmask mask = new OpCodeBitmask ();
270
Console.WriteLine (mask);