├── .nuget ├── NuGet.exe ├── NuGet.Config └── NuGet.targets ├── MethodCache.Tests.TestAssembly ├── MethodResult.cs ├── Cache │ ├── EnumParameter.cs │ ├── ICache.cs │ ├── ComplexCacheAttribute │ │ ├── EnumType.cs │ │ └── CacheAttribute.cs │ ├── ICacheWithRemove.cs │ ├── CacheAttribute.cs │ ├── ICacheWithStoreParameters.cs │ └── DictionaryCache.cs ├── TestClass4.cs ├── TestClass5.cs ├── TestClass6.cs ├── TestClass9.cs ├── TestClass8.cs ├── TestClass1.cs ├── TestClass2.cs ├── TestClass7.cs ├── TestClass3.cs ├── TestClassAllExplicitlyIncluded.cs ├── TestClassStaticProperties.cs ├── TestClassMethodsExcluded.cs ├── TestClassPropertiesExcluded.cs ├── TestClassGeneric.cs ├── TestClassIndividualProperties.cs ├── TestClassWithPropertiesWithoutRemove.cs ├── TestClassWithProperties.cs ├── Properties │ └── AssemblyInfo.cs ├── MethodCache.Tests.TestAssembly.csproj └── TestClassWithParameterizedCacheAttribute.cs ├── MethodCache.Fody ├── packages.config ├── MethodsForWeaving.cs ├── Properties │ └── AssemblyInfo.cs ├── MethodCache.Fody.csproj ├── ReferenceFinder.cs ├── CecilHelper.cs └── ModuleWeaver.cs ├── MethodCache.Attributes ├── Members.cs ├── NoCacheAttribute.cs ├── CacheAttribute.cs ├── Properties │ └── AssemblyInfo.cs └── MethodCache.Attributes.csproj ├── packages └── repositories.config ├── MethodCache.Tests ├── packages.config ├── ModuleWeaverTestsBase.cs ├── Verifier.cs ├── AssemblyResolverMock.cs ├── Properties │ └── AssemblyInfo.cs ├── WeaverHelper.cs ├── GenericTests.cs ├── MethodCacheDisabledAtConfigTests.cs ├── PropertyCacheDisabledAtConfigTests.cs ├── MethodCache.Tests.csproj ├── ModuleWeaverTests.cs ├── PropertyCachingTests.cs └── ParameterizedCacheAttributeWithParameterTests.cs ├── MethodCache.Nuget ├── make.bat ├── readme.txt ├── tools │ ├── uninstall.ps1 │ └── install.ps1 └── MethodCache.nuspec ├── .gitattributes ├── MethodCache.sln ├── .gitignore ├── Settings.StyleCop ├── README.md └── MethodCache.sln.DotSettings /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dresel/MethodCache/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/MethodResult.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | public class MethodResult 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /MethodCache.Fody/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/Cache/EnumParameter.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly.Cache 2 | { 3 | public enum EnumParameter 4 | { 5 | Entry1, 6 | 7 | Entry2 8 | } 9 | } -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MethodCache.Attributes/Members.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Attributes 2 | { 3 | using System; 4 | 5 | [Flags] 6 | public enum Members 7 | { 8 | Methods = 1, 9 | 10 | Properties = 2, 11 | 12 | All = 3 13 | } 14 | } -------------------------------------------------------------------------------- /packages/repositories.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/Cache/ICache.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly.Cache 2 | { 3 | public interface ICache 4 | { 5 | bool Contains(string key); 6 | 7 | T Retrieve(string key); 8 | 9 | void Store(string key, object data); 10 | } 11 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/Cache/ComplexCacheAttribute/EnumType.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly.Cache.ComplexCacheAttribute 2 | { 3 | using System; 4 | 5 | [Flags()] 6 | public enum EnumType 7 | { 8 | Entry1 = 1, 9 | 10 | Entry2 = 2, 11 | 12 | Entry3 = 4 13 | } 14 | } -------------------------------------------------------------------------------- /MethodCache.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/Cache/ICacheWithRemove.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly.Cache 2 | { 3 | public interface ICacheWithRemove 4 | { 5 | bool Contains(string key); 6 | 7 | void Remove(string key); 8 | 9 | T Retrieve(string key); 10 | 11 | void Store(string key, object data); 12 | } 13 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/Cache/CacheAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly.Cache 2 | { 3 | using System; 4 | 5 | public class CacheAttribute : Attribute 6 | { 7 | public string Parameter1 { get; set; } 8 | 9 | public int Parameter2 { get; set; } 10 | 11 | public bool Parameter3 { get; set; } 12 | 13 | public double parameter3; 14 | } 15 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClass4.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | 5 | [Cache] 6 | public class TestClass4 7 | { 8 | // No CacheGetter - ModuleWeaver should check for Cache Getter and should skip weaving of this class 9 | public int MethodOne(int x) 10 | { 11 | return x * x; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/Cache/ICacheWithStoreParameters.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly.Cache 2 | { 3 | using System.Collections.Generic; 4 | 5 | public interface ICacheWithStoreParameters 6 | { 7 | bool Contains(string key); 8 | 9 | T Retrieve(string key); 10 | 11 | void Store(string key, object data, IDictionary parameters); 12 | } 13 | } -------------------------------------------------------------------------------- /MethodCache.Nuget/make.bat: -------------------------------------------------------------------------------- 1 | mkdir input\lib\ 2 | del /Q input\lib\*.* 3 | 4 | msbuild ..\MethodCache.Attributes\MethodCache.Attributes.csproj /p:Configuration=Release;OutputPath=..\MethodCache.Nuget\input\lib 5 | msbuild ..\MethodCache.Fody\MethodCache.Fody.csproj /p:Configuration=Release;OutputPath=..\MethodCache.Nuget\input\lib 6 | 7 | mkdir output 8 | ..\.nuget\nuget.exe pack /o output .\MethodCache.nuspec -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClass5.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | 5 | [Cache] 6 | public class TestClass5 7 | { 8 | // Wrong CacheType - ModuleWeaver should check Getter Type and should skip weaving of this class 9 | public int Cache { get; set; } 10 | 11 | public int MethodOne(int x) 12 | { 13 | return x * x; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /MethodCache.Attributes/NoCacheAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Attributes 2 | { 3 | using System; 4 | 5 | /// 6 | /// Prevents caching the output of this method when CacheAttribute is used on class level. 7 | /// 8 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 9 | public class NoCacheAttribute : Attribute 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClass6.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | public class TestClass6 : TestClass1 7 | { 8 | public TestClass6(ICache cache) 9 | : base(cache) 10 | { 11 | } 12 | 13 | [Attributes.Cache] 14 | public int MethodThree(int x) 15 | { 16 | return x * x * x; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClass9.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache] 7 | public class TestClass9 8 | { 9 | // MethodOne is static, Getter is not - ModuleWeaver should check this and should skip weaving of this method 10 | public ICache Cache { get; set; } 11 | 12 | public static int MethodOne(int x) 13 | { 14 | return x * x; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClass8.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache] 7 | public class TestClass8 8 | { 9 | public static ICache Cache { get; set; } 10 | 11 | public static int MethodOne(int x) 12 | { 13 | return x * x; 14 | } 15 | 16 | public static MethodResult MethodTwo(string x) 17 | { 18 | return new MethodResult(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /MethodCache.Nuget/readme.txt: -------------------------------------------------------------------------------- 1 | Since 1.5.0.0 it is now possible to use custom CacheAttribute parameters for fine-grained cache control (see https://github.com/Dresel/MethodCache section "CacheAttribute Parameters Support" for details). 2 | 3 | Since 1.4.0.0 it is now possible to also cache properties (see https://github.com/Dresel/MethodCache for details). 4 | 5 | If you want this package to behave as previous packages (let class level [Cache] attribute only cache methods), modify ModuleWeavers.xml to: 6 | 7 | -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClass1.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache] 7 | public class TestClass1 8 | { 9 | public TestClass1(ICache cache) 10 | { 11 | Cache = cache; 12 | } 13 | 14 | public ICache Cache { get; set; } 15 | 16 | public int MethodOne(int x) 17 | { 18 | return x * x; 19 | } 20 | 21 | public MethodResult MethodTwo(string x) 22 | { 23 | return new MethodResult(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClass2.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | public class TestClass2 7 | { 8 | public TestClass2(ICache cache) 9 | { 10 | Cache = cache; 11 | } 12 | 13 | public ICache Cache { get; set; } 14 | 15 | [Attributes.Cache] 16 | public int MethodOne(int x) 17 | { 18 | return x * x; 19 | } 20 | 21 | public MethodResult MethodTwo(string x) 22 | { 23 | return new MethodResult(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /MethodCache.Attributes/CacheAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Attributes 2 | { 3 | using System; 4 | 5 | /// 6 | /// Cache the output of this member or members of this class. 7 | /// 8 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, 9 | Inherited = false)] 10 | public class CacheAttribute : Attribute 11 | { 12 | public CacheAttribute() 13 | : this(Members.All) 14 | { 15 | } 16 | 17 | public CacheAttribute(Members membersToCache) 18 | { 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClass7.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache] 7 | public class TestClass7 8 | { 9 | public TestClass7(ICache cache) 10 | { 11 | Cache = cache; 12 | } 13 | 14 | public ICache Cache { get; set; } 15 | 16 | public int MethodOne(int x) 17 | { 18 | return x * x; 19 | } 20 | 21 | [NoCache] 22 | public MethodResult MethodTwo(string x) 23 | { 24 | return new MethodResult(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClass3.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache] 7 | public class TestClass3 8 | { 9 | public TestClass3(DictionaryCache cache) 10 | { 11 | Cache = cache; 12 | } 13 | 14 | public DictionaryCache Cache { get; set; } 15 | 16 | public int MethodOne(int x) 17 | { 18 | return x * x; 19 | } 20 | 21 | public MethodResult MethodTwo(string x) 22 | { 23 | return new MethodResult(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClassAllExplicitlyIncluded.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache(Members.All)] 7 | public class TestClassAllExplicitlyIncluded 8 | { 9 | public TestClassAllExplicitlyIncluded(ICache cache) 10 | { 11 | Cache = cache; 12 | } 13 | 14 | public ICache Cache { get; private set; } 15 | 16 | public string Property 17 | { 18 | get { return "some value"; } 19 | } 20 | 21 | public int Method(int x) 22 | { 23 | return x * x; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClassStaticProperties.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache] 7 | public class TestClassStaticProperties 8 | { 9 | private static int someValue; 10 | 11 | public static ICacheWithRemove Cache { get; set; } 12 | 13 | public static string ReadOnlyProperty 14 | { 15 | get { return "some value"; } 16 | } 17 | 18 | // ReSharper disable once ConvertToAutoProperty 19 | public static int ReadWriteProperty 20 | { 21 | get { return someValue; } 22 | set { someValue = value; } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /MethodCache.Tests/ModuleWeaverTestsBase.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests 2 | { 3 | using System.Reflection; 4 | using System.Xml.Linq; 5 | using NUnit.Framework; 6 | 7 | [TestFixture] 8 | public abstract class ModuleWeaverTestsBase 9 | { 10 | private Assembly assembly; 11 | 12 | protected Assembly Assembly 13 | { 14 | get { return this.assembly; } 15 | } 16 | 17 | protected virtual XElement WeaverConfig 18 | { 19 | get { return null; } 20 | } 21 | 22 | [SetUp] 23 | public void ClassInitialize() 24 | { 25 | if (this.assembly == null) 26 | { 27 | this.assembly = WeaverHelper.WeaveAssembly(GetType().Name, WeaverConfig); 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClassMethodsExcluded.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache(Members.Properties)] 7 | public class TestClassMethodsExcluded 8 | { 9 | public TestClassMethodsExcluded(ICache cache) 10 | { 11 | Cache = cache; 12 | } 13 | 14 | public ICache Cache { get; private set; } 15 | 16 | public string Property 17 | { 18 | get { return "some value"; } 19 | } 20 | 21 | public int Method(int x) 22 | { 23 | return x * x; 24 | } 25 | 26 | [Attributes.Cache] 27 | public int MethodIncluded(int x) 28 | { 29 | return x * x; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClassPropertiesExcluded.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache(Members.Methods)] 7 | public class TestClassPropertiesExcluded 8 | { 9 | public TestClassPropertiesExcluded(ICache cache) 10 | { 11 | Cache = cache; 12 | } 13 | 14 | public ICache Cache { get; private set; } 15 | 16 | [Attributes.Cache] 17 | public string ExplicitlyCachedProperty 18 | { 19 | get { return "some value"; } 20 | } 21 | 22 | public string ReadOnlyProperty 23 | { 24 | get { return "some value"; } 25 | } 26 | 27 | public int Method(int x) 28 | { 29 | return x * x; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClassGeneric.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache] 7 | public class TestClassGeneric 8 | { 9 | private TClass property; 10 | 11 | public TestClassGeneric(ICacheWithRemove cache) 12 | { 13 | Cache = cache; 14 | } 15 | 16 | public static ICacheWithRemove Cache { get; private set; } 17 | 18 | // ReSharper disable once ConvertToAutoProperty 19 | public TClass Property 20 | { 21 | get { return this.property; } 22 | set { this.property = value; } 23 | } 24 | 25 | public TMethod Method1(TMethod parameter) 26 | { 27 | return parameter; 28 | } 29 | 30 | public TClass Method2(TClass parameter) 31 | { 32 | return parameter; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /MethodCache.Nuget/tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | 4 | function Update-FodyConfig($addinName, $project) 5 | { 6 | $fodyWeaversPath = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($project.FullName), "FodyWeavers.xml") 7 | 8 | if (!(Test-Path ($fodyWeaversPath))) 9 | { 10 | return 11 | } 12 | 13 | $env:FodyLastProjectPath = $project.FullName 14 | $env:FodyLastWeaverName = $addinName 15 | $env:FodyLastXmlContents = [IO.File]::ReadAllText($fodyWeaversPath) 16 | 17 | $xml = [xml](get-content $fodyWeaversPath) 18 | 19 | $weavers = $xml["Weavers"] 20 | $node = $weavers.SelectSingleNode($addinName) 21 | 22 | if ($node) 23 | { 24 | $weavers.RemoveChild($node) 25 | } 26 | 27 | $xml.Save($fodyWeaversPath) 28 | } 29 | 30 | 31 | 32 | Update-FodyConfig $package.Id.Replace(".Fody", "") $project -------------------------------------------------------------------------------- /MethodCache.Tests/Verifier.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests 2 | { 3 | using System.Diagnostics; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using Microsoft.Build.Utilities; 7 | 8 | public static class Verifier 9 | { 10 | public static string Verify(string assemblyPath2) 11 | { 12 | string exePath = GetPathToPEVerify(); 13 | Process process = 14 | Process.Start(new ProcessStartInfo(exePath, "\"" + assemblyPath2 + "\"") 15 | { 16 | RedirectStandardOutput = true, 17 | UseShellExecute = false, 18 | CreateNoWindow = true 19 | }); 20 | 21 | process.WaitForExit(10000); 22 | 23 | return process.StandardOutput.ReadToEnd().Trim(); 24 | } 25 | 26 | private static string GetPathToPEVerify() 27 | { 28 | return Path.Combine(ToolLocationHelper.GetPathToDotNetFrameworkSdk(TargetDotNetFrameworkVersion.VersionLatest), 29 | @"bin\NETFX 4.0 Tools\peverify.exe"); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClassIndividualProperties.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | public class TestClassIndividualProperties 7 | { 8 | private string field; 9 | 10 | private int someValue; 11 | 12 | public TestClassIndividualProperties(ICacheWithRemove cache) 13 | { 14 | Cache = cache; 15 | } 16 | 17 | [Attributes.Cache] 18 | public int AutoProperty { get; set; } 19 | 20 | public ICacheWithRemove Cache { get; private set; } 21 | 22 | [Attributes.Cache] 23 | public string ReadOnlyProperty 24 | { 25 | get { return "some value"; } 26 | } 27 | 28 | [Attributes.Cache] 29 | 30 | // ReSharper disable once ConvertToAutoProperty 31 | public int ReadWriteProperty 32 | { 33 | get { return this.someValue; } 34 | set { this.someValue = value; } 35 | } 36 | 37 | [Attributes.Cache] 38 | public string SetOnlyProperty 39 | { 40 | set { this.field = value; } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClassWithPropertiesWithoutRemove.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache] 7 | public class TestClassWithPropertiesWithoutRemove 8 | { 9 | private string field; 10 | 11 | private int someValue; 12 | 13 | public TestClassWithPropertiesWithoutRemove(ICache cache) 14 | { 15 | Cache = cache; 16 | } 17 | 18 | public int AutoProperty { get; set; } 19 | 20 | public ICache Cache { get; private set; } 21 | 22 | [NoCache] 23 | public string ReadOnlyNoCache 24 | { 25 | get { return "no cache value"; } 26 | } 27 | 28 | public string ReadOnlyProperty 29 | { 30 | get { return "some value"; } 31 | } 32 | 33 | // ReSharper disable once ConvertToAutoProperty 34 | public int ReadWriteProperty 35 | { 36 | get { return this.someValue; } 37 | set { this.someValue = value; } 38 | } 39 | 40 | public string SetOnlyProperty 41 | { 42 | set { this.field = value; } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /MethodCache.Tests/AssemblyResolverMock.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | using Mono.Cecil; 6 | 7 | public class AssemblyResolverMock : IAssemblyResolver 8 | { 9 | public AssemblyDefinition Resolve(AssemblyNameReference name) 10 | { 11 | throw new NotImplementedException(); 12 | } 13 | 14 | public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | 19 | public AssemblyDefinition Resolve(string fullName) 20 | { 21 | if (fullName == "System") 22 | { 23 | string codeBase = typeof(Debug).Assembly.CodeBase.Replace("file:///", ""); 24 | return AssemblyDefinition.ReadAssembly(codeBase); 25 | } 26 | else 27 | { 28 | string codeBase = typeof(string).Assembly.CodeBase.Replace("file:///", ""); 29 | return AssemblyDefinition.ReadAssembly(codeBase); 30 | } 31 | } 32 | 33 | public AssemblyDefinition Resolve(string fullName, ReaderParameters parameters) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClassWithProperties.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using MethodCache.Attributes; 4 | using MethodCache.Tests.TestAssembly.Cache; 5 | 6 | [Attributes.Cache] 7 | public class TestClassWithProperties 8 | { 9 | private string field; 10 | 11 | private int someValue; 12 | 13 | public TestClassWithProperties(ICacheWithRemove cache) 14 | { 15 | Cache = cache; 16 | ReadOnlyAutoProperty = "some value"; 17 | } 18 | 19 | public int AutoProperty { get; set; } 20 | 21 | public ICacheWithRemove Cache { get; private set; } 22 | 23 | [NoCache] 24 | public string ReadOnlyNoCache 25 | { 26 | get { return "no cache value"; } 27 | } 28 | 29 | public string ReadOnlyAutoProperty { get; } 30 | 31 | public string ReadOnlyProperty 32 | { 33 | get { return "some value"; } 34 | } 35 | 36 | // ReSharper disable once ConvertToAutoProperty 37 | public int ReadWriteProperty 38 | { 39 | get { return someValue; } 40 | set { someValue = value; } 41 | } 42 | 43 | public string SetOnlyProperty 44 | { 45 | set { field = value; } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /MethodCache.Attributes/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("MethodCache.Attributes")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("MethodCache.Attributes")] 12 | [assembly: AssemblyCopyright("Copyright © 2013")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | [assembly: NeutralResourcesLanguage("en")] 16 | 17 | // Version information for an assembly consists of the following four values: 18 | // Major Version 19 | // Minor Version 20 | // Build Number 21 | // Revision 22 | // You can specify all the values or you can default the Build and Revision Numbers 23 | // by using the '*' as shown below: 24 | // [assembly: AssemblyVersion("1.0.*")] 25 | [assembly: AssemblyVersion("1.5.1.0")] 26 | [assembly: AssemblyFileVersion("1.5.1.0")] -------------------------------------------------------------------------------- /MethodCache.Fody/MethodsForWeaving.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Fody 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using Mono.Cecil; 6 | 7 | internal class MethodsForWeaving 8 | { 9 | private readonly IList> methods; 10 | 11 | private readonly IList> properties; 12 | 13 | public MethodsForWeaving() 14 | { 15 | this.methods = new List>(); 16 | this.properties = new List>(); 17 | } 18 | 19 | public IEnumerable> Methods 20 | { 21 | get { return this.methods; } 22 | } 23 | 24 | public IEnumerable> Properties 25 | { 26 | get { return this.properties; } 27 | } 28 | 29 | public void Add(MethodDefinition method, CustomAttribute attribute) 30 | { 31 | this.methods.Add(new Tuple(method, attribute)); 32 | } 33 | 34 | public void Add(PropertyDefinition property, CustomAttribute attribute) 35 | { 36 | if (property.GetMethod == null) 37 | { 38 | return; 39 | } 40 | 41 | this.properties.Add(new Tuple(property, attribute)); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /MethodCache.Fody/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("MethodCache.Fody")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("MethodCache.Fody")] 12 | [assembly: AssemblyCopyright("Copyright © 2013")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("dead2628-829b-4d50-82e3-2a33e8c5e15c")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // Major Version 26 | // Minor Version 27 | // Build Number 28 | // Revision 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.5.1.0")] 33 | [assembly: AssemblyFileVersion("1.5.1.0")] -------------------------------------------------------------------------------- /MethodCache.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("MethodCache.Tests")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("MethodCache.Tests")] 12 | [assembly: AssemblyCopyright("Copyright © 2013")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("da9101e3-3bc8-4f74-8d60-12482624b893")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // Major Version 26 | // Minor Version 27 | // Build Number 28 | // Revision 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.5.1.0")] 33 | [assembly: AssemblyFileVersion("1.5.1.0")] -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/Cache/DictionaryCache.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly.Cache 2 | { 3 | using System.Collections.Generic; 4 | 5 | public class DictionaryCache : ICache, ICacheWithRemove, ICacheWithStoreParameters 6 | { 7 | public DictionaryCache() 8 | { 9 | Storage = new Dictionary(); 10 | } 11 | 12 | public int NumRemoveCalls { get; private set; } 13 | 14 | public int NumRetrieveCalls { get; private set; } 15 | 16 | public int NumStoreCalls { get; private set; } 17 | 18 | public int NumStoreParameterCalls { get; private set; } 19 | 20 | public IDictionary ParametersPassedToLastStoreCall { get; set; } 21 | 22 | private Dictionary Storage { get; } 23 | 24 | public bool Contains(string key) 25 | { 26 | return Storage.ContainsKey(key); 27 | } 28 | 29 | public void Remove(string key) 30 | { 31 | NumRemoveCalls++; 32 | 33 | Storage.Remove(key); 34 | } 35 | 36 | public T Retrieve(string key) 37 | { 38 | NumRetrieveCalls++; 39 | 40 | return (T)Storage[key]; 41 | } 42 | 43 | public void Store(string key, object data) 44 | { 45 | NumStoreCalls++; 46 | 47 | Storage[key] = data; 48 | } 49 | 50 | void ICacheWithStoreParameters.Store(string key, object data, IDictionary parameters) 51 | { 52 | NumStoreParameterCalls++; 53 | 54 | ParametersPassedToLastStoreCall = new Dictionary(parameters); 55 | 56 | Storage[key] = data; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("MethodCache.Tests.TestAssembly")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("MethodCache.Tests.TestAssembly")] 12 | [assembly: AssemblyCopyright("Copyright © 2013")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("7f0009f5-0a9c-41fc-9025-72cf30f516d9")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // Major Version 26 | // Minor Version 27 | // Build Number 28 | // Revision 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.5.1.0")] 33 | [assembly: AssemblyFileVersion("1.5.1.0")] -------------------------------------------------------------------------------- /MethodCache.Nuget/MethodCache.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MethodCache.Fody 5 | 1.5.1 6 | MethodCache.Fody 7 | Christopher Dresel 8 | Christopher Dresel 9 | http://www.opensource.org/licenses/mit-license.php 10 | http://github.com/Dresel/MethodCache 11 | false 12 | A method and property caching Fody addin. 13 | 14 | en-US 15 | ILWeaving, Fody, Cecil, Caching 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /MethodCache.Nuget/tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | 4 | function RemoveForceProjectLevelHack($project) 5 | { 6 | Foreach ($item in $project.ProjectItems) 7 | { 8 | if ($item.Name -eq "Fody_ToBeDeleted.txt") 9 | { 10 | $item.Delete() 11 | } 12 | } 13 | } 14 | 15 | function Update-FodyConfig($addinName, $project) 16 | { 17 | 18 | $fodyWeaversPath = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($project.FullName), "FodyWeavers.xml") 19 | 20 | $FodyLastProjectPath = $env:FodyLastProjectPath 21 | $FodyLastWeaverName = $env:FodyLastWeaverName 22 | $FodyLastXmlContents = $env:FodyLastXmlContents 23 | 24 | if ( 25 | ($FodyLastProjectPath -eq $project.FullName) -and 26 | ($FodyLastWeaverName -eq $addinName)) 27 | { 28 | [System.IO.File]::WriteAllText($fodyWeaversPath, $FodyLastXmlContents) 29 | return 30 | } 31 | 32 | $xml = [xml](get-content $fodyWeaversPath) 33 | 34 | $weavers = $xml["Weavers"] 35 | $node = $weavers.SelectSingleNode($addinName) 36 | 37 | if (-not $node) 38 | { 39 | $newNode = $xml.CreateElement($addinName) 40 | $weavers.AppendChild($newNode) 41 | } 42 | 43 | $xml.Save($fodyWeaversPath) 44 | } 45 | 46 | function Fix-ReferencesCopyLocal($package, $project) 47 | { 48 | $asms = $package.AssemblyReferences | %{$_.Name} 49 | 50 | foreach ($reference in $project.Object.References) 51 | { 52 | if ($asms -contains $reference.Name + ".dll") 53 | { 54 | if($reference.CopyLocal -eq $true) 55 | { 56 | $reference.CopyLocal = $false; 57 | } 58 | } 59 | } 60 | } 61 | 62 | RemoveForceProjectLevelHack $project 63 | 64 | Update-FodyConfig $package.Id.Replace(".Fody", "") $project 65 | 66 | Fix-ReferencesCopyLocal $package $project -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/Cache/ComplexCacheAttribute/CacheAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly.Cache.ComplexCacheAttribute 2 | { 3 | using System; 4 | 5 | public class CacheAttribute : Attribute 6 | { 7 | public bool ParameterBool { get; set; } 8 | 9 | public bool[] ParameterBoolArray { get; set; } 10 | 11 | public byte ParameterByte { get; set; } 12 | 13 | public byte[] ParameterByteArray { get; set; } 14 | 15 | public char ParameterCharacter { get; set; } 16 | 17 | public char[] ParameterCharacterArray { get; set; } 18 | 19 | public double ParameterDouble { get; set; } 20 | 21 | public double[] ParameterDoubleArray { get; set; } 22 | 23 | public EnumType ParameterEnum { get; set; } 24 | 25 | public EnumType[] ParameterEnumArray { get; set; } 26 | 27 | public float ParameterFloat { get; set; } 28 | 29 | public float[] ParameterFloatArray { get; set; } 30 | 31 | public int ParameterInt { get; set; } 32 | 33 | public int[] ParameterIntArray { get; set; } 34 | 35 | public long ParameterLong { get; set; } 36 | 37 | public long[] ParameterLongArray { get; set; } 38 | 39 | public object ParameterObject { get; set; } 40 | 41 | public object[] ParameterObjectArray { get; set; } 42 | 43 | public sbyte ParameterSByte { get; set; } 44 | 45 | public sbyte[] ParameterSByteArray { get; set; } 46 | 47 | public short ParameterShort { get; set; } 48 | 49 | public short[] ParameterShortArray { get; set; } 50 | 51 | public string ParameterString { get; set; } 52 | 53 | public string[] ParameterStringArray { get; set; } 54 | 55 | public Type ParameterType { get; set; } 56 | 57 | public Type[] ParameterTypeArray { get; set; } 58 | 59 | public uint ParameterUInt { get; set; } 60 | 61 | public uint[] ParameterUIntArray { get; set; } 62 | 63 | public ulong ParameterULong { get; set; } 64 | 65 | public ulong[] ParameterULongArray { get; set; } 66 | 67 | public ushort ParameterUShort { get; set; } 68 | 69 | public ushort[] ParameterUShortArray { get; set; } 70 | } 71 | } -------------------------------------------------------------------------------- /MethodCache.Tests/WeaverHelper.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Reflection; 7 | using System.Xml.Linq; 8 | using MethodCache.Fody; 9 | using Mono.Cecil; 10 | 11 | public class WeaverHelper 12 | { 13 | public static dynamic CreateInstance(Assembly assembly) 14 | { 15 | return Activator.CreateInstance(CreateType(assembly)); 16 | } 17 | 18 | public static dynamic CreateInstance(Assembly assembly, object parameter) 19 | { 20 | return Activator.CreateInstance(CreateType(assembly), parameter); 21 | } 22 | 23 | public static dynamic CreateInstance(Assembly assembly, object[] parameters) 24 | { 25 | return Activator.CreateInstance(CreateType(assembly), parameters); 26 | } 27 | 28 | public static Type CreateType(Assembly assembly) 29 | { 30 | return assembly.GetType(typeof(T).FullName); 31 | } 32 | 33 | public static Assembly WeaveAssembly(string suffix, XElement config) 34 | { 35 | string projectPath = 36 | Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, 37 | @"..\..\..\MethodCache.Tests.TestAssembly\MethodCache.Tests.TestAssembly.csproj")); 38 | string assemblyPath = Path.Combine(Path.GetDirectoryName(projectPath), 39 | @"bin\Debug\MethodCache.Tests.TestAssembly.dll"); 40 | 41 | #if (!DEBUG) 42 | assemblyPath = assemblyPath.Replace("Debug", "Release"); 43 | #endif 44 | 45 | string newAssembly = assemblyPath.Replace(".dll", string.Format("{0}.dll", suffix)); 46 | File.Copy(assemblyPath, newAssembly, true); 47 | 48 | ModuleDefinition moduleDefinition = ModuleDefinition.ReadModule(newAssembly); 49 | ModuleWeaver weavingTask = new ModuleWeaver { ModuleDefinition = moduleDefinition }; 50 | 51 | weavingTask.AssemblyResolver = new AssemblyResolverMock(); 52 | 53 | weavingTask.LogInfo = (message) => Debug.WriteLine(message); 54 | weavingTask.LogWarning = (message) => Debug.WriteLine(message); 55 | weavingTask.LogError = (message) => new Exception(message); 56 | 57 | if (config != null) 58 | { 59 | weavingTask.Config = config; 60 | } 61 | 62 | #if (DEBUG) 63 | weavingTask.DefineConstants.Add("DEBUG"); 64 | #endif 65 | 66 | weavingTask.Execute(); 67 | moduleDefinition.Write(newAssembly); 68 | 69 | return Assembly.LoadFile(newAssembly); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /MethodCache.Attributes/MethodCache.Attributes.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10.0 6 | Debug 7 | AnyCPU 8 | {4F4D865D-8ABB-4554-84BB-2B7BC40E37F9} 9 | Library 10 | Properties 11 | MethodCache.Attributes 12 | MethodCache.Attributes 13 | v4.0 14 | Profile328 15 | 512 16 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 52 | -------------------------------------------------------------------------------- /MethodCache.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MethodCache.Fody", "MethodCache.Fody\MethodCache.Fody.csproj", "{1A9A6C8E-6B88-4FF1-9447-E37CB39AAFDE}" 5 | EndProject 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{725AD6BA-EA0A-4096-8688-A295844613E7}" 7 | ProjectSection(SolutionItems) = preProject 8 | .nuget\NuGet.Config = .nuget\NuGet.Config 9 | .nuget\NuGet.exe = .nuget\NuGet.exe 10 | .nuget\NuGet.targets = .nuget\NuGet.targets 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MethodCache.Attributes", "MethodCache.Attributes\MethodCache.Attributes.csproj", "{4F4D865D-8ABB-4554-84BB-2B7BC40E37F9}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MethodCache.Tests.TestAssembly", "MethodCache.Tests.TestAssembly\MethodCache.Tests.TestAssembly.csproj", "{CBF07605-D646-45F7-B2B0-54DC33538644}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MethodCache.Tests", "MethodCache.Tests\MethodCache.Tests.csproj", "{CF7379B0-EC94-4427-871E-DBD537959C09}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {1A9A6C8E-6B88-4FF1-9447-E37CB39AAFDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {1A9A6C8E-6B88-4FF1-9447-E37CB39AAFDE}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {1A9A6C8E-6B88-4FF1-9447-E37CB39AAFDE}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {1A9A6C8E-6B88-4FF1-9447-E37CB39AAFDE}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {4F4D865D-8ABB-4554-84BB-2B7BC40E37F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {4F4D865D-8ABB-4554-84BB-2B7BC40E37F9}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {4F4D865D-8ABB-4554-84BB-2B7BC40E37F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {4F4D865D-8ABB-4554-84BB-2B7BC40E37F9}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {CBF07605-D646-45F7-B2B0-54DC33538644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {CBF07605-D646-45F7-B2B0-54DC33538644}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {CBF07605-D646-45F7-B2B0-54DC33538644}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {CBF07605-D646-45F7-B2B0-54DC33538644}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {CF7379B0-EC94-4427-871E-DBD537959C09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {CF7379B0-EC94-4427-871E-DBD537959C09}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {CF7379B0-EC94-4427-871E-DBD537959C09}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {CF7379B0-EC94-4427-871E-DBD537959C09}.Release|Any CPU.Build.0 = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(SolutionProperties) = preSolution 43 | HideSolutionNode = FALSE 44 | EndGlobalSection 45 | EndGlobal 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | 98 | # NuGet Packages Directory 99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 100 | packages/ 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | sql/ 111 | *.Cache 112 | ClientBin/ 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *~ 116 | *.dbmdl 117 | *.[Pp]ublish.xml 118 | *.pfx 119 | *.publishsettings 120 | 121 | # RIA/Silverlight projects 122 | Generated_Code/ 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | UpgradeLog*.htm 130 | 131 | # SQL Server files 132 | App_Data/*.mdf 133 | App_Data/*.ldf 134 | 135 | 136 | #LightSwitch generated files 137 | GeneratedArtifacts/ 138 | _Pvt_Extensions/ 139 | ModelManifest.xml 140 | 141 | # ========================= 142 | # Windows detritus 143 | # ========================= 144 | 145 | # Windows image file caches 146 | Thumbs.db 147 | ehthumbs.db 148 | 149 | # Folder config file 150 | Desktop.ini 151 | 152 | # Recycle Bin used on file shares 153 | $RECYCLE.BIN/ 154 | 155 | # Mac desktop service store files 156 | .DS_Store 157 | -------------------------------------------------------------------------------- /MethodCache.Tests/GenericTests.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests 2 | { 3 | using System; 4 | using MethodCache.Tests.TestAssembly; 5 | using MethodCache.Tests.TestAssembly.Cache; 6 | using NUnit.Framework; 7 | 8 | public class GenericTests : ModuleWeaverTestsBase 9 | { 10 | [Test] 11 | public void GenericClassReferenceTypeExecutesCorrectly() 12 | { 13 | // Arrange 14 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 15 | dynamic instance = WeaverHelper.CreateInstance>>(Assembly, cache); 16 | 17 | // Act 18 | dynamic value1 = instance.Method1>(new Tuple(1, 2.0)); 19 | value1 = instance.Method1>(new Tuple(1, 2.0)); 20 | dynamic value2 = instance.Method2(new Tuple(1, 2.0)); 21 | value2 = instance.Method2(new Tuple(1, 2.0)); 22 | 23 | // Assert 24 | Assert.IsTrue(cache.NumStoreCalls == 2); 25 | Assert.IsTrue(cache.NumRetrieveCalls == 2); 26 | } 27 | 28 | [Test] 29 | public void GenericClassValueTypeExecutesCorrectly() 30 | { 31 | // Arrange 32 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 33 | dynamic instance = WeaverHelper.CreateInstance>(Assembly, cache); 34 | 35 | // Act 36 | dynamic value1 = instance.Method1(10); 37 | value1 = instance.Method1(10); 38 | dynamic value2 = instance.Method2(10); 39 | value2 = instance.Method2(10); 40 | 41 | // Assert 42 | Assert.IsTrue(cache.NumStoreCalls == 2); 43 | Assert.IsTrue(cache.NumRetrieveCalls == 2); 44 | } 45 | 46 | [Test] 47 | public void GenericClassValueTypePropertyExecutesCorrectly() 48 | { 49 | // Arrange 50 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 51 | dynamic instance = WeaverHelper.CreateInstance>(Assembly, cache); 52 | 53 | // Act 54 | dynamic value = instance.Property; 55 | value = instance.Property; 56 | instance.Property = value; 57 | 58 | // Assert 59 | Assert.IsTrue(cache.NumStoreCalls == 1); 60 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 61 | Assert.IsTrue(cache.NumRemoveCalls == 1); 62 | } 63 | 64 | [Test] 65 | public void GenericClassReferenceTypePropertyExecutesCorrectly() 66 | { 67 | // Arrange 68 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 69 | dynamic instance = WeaverHelper.CreateInstance>>(Assembly, cache); 70 | 71 | // Act 72 | instance.Property = new Tuple(1, 2.0); 73 | dynamic value = instance.Property; 74 | value = instance.Property; 75 | instance.Property = value; 76 | 77 | // Assert 78 | Assert.IsTrue(cache.NumStoreCalls == 1); 79 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 80 | Assert.IsTrue(cache.NumRemoveCalls == 2); 81 | } 82 | 83 | [Test] 84 | public void GenericMethod() 85 | { 86 | // Arrange 87 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 88 | dynamic instance = WeaverHelper.CreateInstance>>(Assembly, cache); 89 | 90 | // Act 91 | dynamic value1 = instance.Method1(0); 92 | 93 | // Assert 94 | Assert.IsTrue(cache.Contains(string.Format("MethodCache.Tests.TestAssembly.TestClassGeneric`1.Method1_{0}_{1}", typeof(int), 0))); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /MethodCache.Tests/MethodCacheDisabledAtConfigTests.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests 2 | { 3 | using System; 4 | using System.Reflection; 5 | using System.Xml.Linq; 6 | using MethodCache.Tests.TestAssembly; 7 | using MethodCache.Tests.TestAssembly.Cache; 8 | using NUnit.Framework; 9 | 10 | public class MethodCacheDisabledAtConfigTests : ModuleWeaverTestsBase 11 | { 12 | protected override XElement WeaverConfig 13 | { 14 | get { return new XElement("MethodCache", new XAttribute("CacheMethods", false)); } 15 | } 16 | 17 | [Test] 18 | public void ClassLevelCache_ShouldCacheMethodWhenAllMembersSelectedExplicitlyOnClassAttribute() 19 | { 20 | // Arrange 21 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 22 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 23 | 24 | // Act 25 | dynamic value = instance.Method(10); 26 | value = instance.Method(10); 27 | 28 | // Assert 29 | Assert.That(cache.NumStoreCalls, Is.EqualTo(1)); 30 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(1)); 31 | } 32 | 33 | [Test] 34 | public void ClassLevelCache_ShouldCacheMethodWhenSelectedExplicitlyOnClassAttribute() 35 | { 36 | // Arrange 37 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 38 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 39 | 40 | // Act 41 | dynamic value = instance.Method(10); 42 | value = instance.Method(10); 43 | 44 | // Assert 45 | Assert.That(cache.NumStoreCalls, Is.EqualTo(1)); 46 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(1)); 47 | } 48 | 49 | [Test] 50 | public void ClassLevelCache_ShouldNotCacheMethods() 51 | { 52 | // Arrange 53 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 54 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 55 | 56 | // Act 57 | dynamic value = instance.MethodOne(10); 58 | value = instance.MethodOne(10); 59 | 60 | // Assert 61 | Assert.That(cache.NumStoreCalls, Is.EqualTo(0)); 62 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(0)); 63 | } 64 | 65 | [Test] 66 | public void ClassLevelCache_ShouldNotCacheStaticMethods() 67 | { 68 | // Arrange 69 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 70 | 71 | Type testClassType = Assembly.GetType(typeof(TestClass8).FullName); 72 | Type cacheType = Assembly.GetType(typeof(ICache).FullName); 73 | 74 | PropertyInfo cacheProperty = testClassType.GetProperty("Cache", cacheType); 75 | cacheProperty.SetValue(null, cache, null); 76 | 77 | MethodInfo method = testClassType.GetMethod("MethodTwo", new[] { typeof(string) }); 78 | 79 | // Act 80 | method.Invoke(null, new object[] { "1337" }); 81 | method.Invoke(null, new object[] { "1337" }); 82 | 83 | // Assert 84 | Assert.That(cache.NumStoreCalls, Is.EqualTo(0)); 85 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(0)); 86 | } 87 | 88 | [Test] 89 | public void IndividualCache_ShouldCacheMethods() 90 | { 91 | // Arrange 92 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 93 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 94 | 95 | // Act 96 | dynamic value = instance.MethodThree(10); 97 | value = instance.MethodThree(10); 98 | 99 | // Assert 100 | Assert.That(cache.NumStoreCalls, Is.EqualTo(1)); 101 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(1)); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /MethodCache.Fody/MethodCache.Fody.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1A9A6C8E-6B88-4FF1-9447-E37CB39AAFDE} 8 | Library 9 | Properties 10 | MethodCache.Fody 11 | MethodCache.Fody 12 | v4.0 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.dll 37 | 38 | 39 | ..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.Mdb.dll 40 | 41 | 42 | ..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.Pdb.dll 43 | 44 | 45 | ..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.Rocks.dll 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Members.cs 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/MethodCache.Tests.TestAssembly.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {CBF07605-D646-45F7-B2B0-54DC33538644} 9 | Library 10 | Properties 11 | MethodCache.Tests.TestAssembly 12 | MethodCache.Tests.TestAssembly 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 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 | {4F4D865D-8ABB-4554-84BB-2B7BC40E37F9} 75 | MethodCache.Attributes 76 | 77 | 78 | 79 | 86 | -------------------------------------------------------------------------------- /MethodCache.Tests/PropertyCacheDisabledAtConfigTests.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests 2 | { 3 | using System; 4 | using System.Reflection; 5 | using System.Xml.Linq; 6 | using MethodCache.Tests.TestAssembly; 7 | using MethodCache.Tests.TestAssembly.Cache; 8 | using NUnit.Framework; 9 | 10 | public class PropertyCacheDisabledAtConfigTests : ModuleWeaverTestsBase 11 | { 12 | protected override XElement WeaverConfig 13 | { 14 | get { return new XElement("MethodCache", new XAttribute("CacheProperties", false)); } 15 | } 16 | 17 | [Test] 18 | public void ClassLevelCache_ShouldCachePropertiesWhenAllMembersSelectedExplicitlyOnClassAttribute() 19 | { 20 | // Arrange 21 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 22 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 23 | 24 | // Act 25 | dynamic value = instance.Property; 26 | value = instance.Property; 27 | 28 | // Assert 29 | Assert.That(cache.NumStoreCalls, Is.EqualTo(1)); 30 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(1)); 31 | } 32 | 33 | [Test] 34 | public void ClassLevelCache_ShouldCachePropertiesWhenSelectedExplicitlyOnClassAttribute() 35 | { 36 | // Arrange 37 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 38 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 39 | 40 | // Act 41 | dynamic value = instance.Property; 42 | value = instance.Property; 43 | 44 | // Assert 45 | Assert.That(cache.NumStoreCalls, Is.EqualTo(1)); 46 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(1)); 47 | } 48 | 49 | [Test] 50 | public void ClassLevelCache_ShouldNotCacheProperties() 51 | { 52 | // Arrange 53 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 54 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 55 | 56 | // Act 57 | dynamic value = instance.ReadOnlyProperty; 58 | value = instance.ReadOnlyProperty; 59 | value = instance.ReadOnlyNoCache; 60 | value = instance.ReadOnlyNoCache; 61 | value = instance.AutoProperty; 62 | value = instance.AutoProperty; 63 | value = instance.ReadWriteProperty; 64 | value = instance.ReadWriteProperty; 65 | 66 | // Assert 67 | Assert.That(cache.NumStoreCalls, Is.EqualTo(0)); 68 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(0)); 69 | } 70 | 71 | [Test] 72 | public void ClassLevelCache_ShouldNotCacheStaticReadOnlyProperties() 73 | { 74 | // Arrange 75 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 76 | 77 | Type testClassType = Assembly.GetType(typeof(TestClassStaticProperties).FullName); 78 | Type cacheType = Assembly.GetType(typeof(ICacheWithRemove).FullName); 79 | 80 | PropertyInfo cacheProperty = testClassType.GetProperty("Cache", cacheType); 81 | cacheProperty.SetValue(null, cache, null); 82 | 83 | PropertyInfo property = testClassType.GetProperty("ReadOnlyProperty"); 84 | 85 | // Act 86 | object value = property.GetValue(null, null); 87 | value = property.GetValue(null, null); 88 | 89 | // Assert 90 | Assert.That(cache.NumStoreCalls, Is.EqualTo(0)); 91 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(0)); 92 | } 93 | 94 | [Test] 95 | public void ClassLevelCache_ShouldNotCacheStaticReadWriteProperties() 96 | { 97 | // Arrange 98 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 99 | 100 | Type testClassType = Assembly.GetType(typeof(TestClassStaticProperties).FullName); 101 | Type cacheType = Assembly.GetType(typeof(ICacheWithRemove).FullName); 102 | 103 | PropertyInfo cacheProperty = testClassType.GetProperty("Cache", cacheType); 104 | cacheProperty.SetValue(null, cache, null); 105 | 106 | PropertyInfo property = testClassType.GetProperty("ReadWriteProperty"); 107 | 108 | // Act 109 | object value = property.GetValue(null, null); 110 | value = property.GetValue(null, null); 111 | 112 | // Assert 113 | Assert.That(cache.NumStoreCalls, Is.EqualTo(0)); 114 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(0)); 115 | } 116 | 117 | [Test] 118 | public void IndividualCache_ShouldCacheEligibleProperties() 119 | { 120 | // Arrange 121 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 122 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 123 | 124 | // Act 125 | dynamic value = instance.ReadOnlyProperty; 126 | value = instance.ReadOnlyProperty; 127 | value = instance.ReadWriteProperty; 128 | value = instance.ReadWriteProperty; 129 | 130 | // Assert 131 | Assert.That(cache.NumStoreCalls, Is.EqualTo(2)); 132 | Assert.That(cache.NumRetrieveCalls, Is.EqualTo(2)); 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /MethodCache.Tests.TestAssembly/TestClassWithParameterizedCacheAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests.TestAssembly 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using MethodCache.Tests.TestAssembly.Cache; 6 | using MethodCache.Tests.TestAssembly.Cache.ComplexCacheAttribute; 7 | 8 | [Cache.Cache(Parameter1 = "CacheParameter", Parameter2 = 1)] 9 | public class TestClassWithParameterizedCacheAttribute 10 | { 11 | public TestClassWithParameterizedCacheAttribute(ICacheWithStoreParameters cache) 12 | { 13 | Cache = cache; 14 | } 15 | 16 | public ICacheWithStoreParameters Cache { get; set; } 17 | 18 | public string CacheParameterMethod(string argument1, int argument2) 19 | { 20 | return argument1 + argument2; 21 | } 22 | 23 | [Cache.Cache(Parameter3 = true, parameter3 = 0)] 24 | public string CacheParameterMethodWithField(string argument1, int argument2) 25 | { 26 | return argument1 + argument2; 27 | } 28 | 29 | [Cache.ComplexCacheAttribute.Cache(ParameterBoolArray = new bool[] { false, true }, 30 | ParameterByteArray = new byte[] { Byte.MinValue, Byte.MaxValue }, 31 | ParameterCharacterArray = new char[] { Char.MinValue, Char.MaxValue }, 32 | ParameterDoubleArray = new double[] { Double.MinValue, Double.MaxValue }, 33 | ParameterFloatArray = new float[] { Single.MinValue, Single.MaxValue }, 34 | ParameterIntArray = new int[] { Int32.MinValue, Int32.MaxValue }, 35 | ParameterLongArray = new long[] { Int64.MinValue, Int64.MaxValue }, 36 | ParameterSByteArray = new sbyte[] { SByte.MinValue, SByte.MaxValue }, 37 | ParameterShortArray = new short[] { Int16.MinValue, Int16.MaxValue }, 38 | ParameterUIntArray = new uint[] { UInt32.MinValue, UInt32.MaxValue }, 39 | ParameterULongArray = new ulong[] { UInt64.MinValue, UInt64.MaxValue }, 40 | ParameterUShortArray = new ushort[] { UInt16.MinValue, UInt16.MaxValue })] 41 | public int ComplexCacheParameterMethodArrays() 42 | { 43 | return 0; 44 | } 45 | 46 | [Cache.ComplexCacheAttribute.Cache(ParameterEnum = EnumType.Entry1 | EnumType.Entry3)] 47 | public int ComplexCacheParameterMethodEnum() 48 | { 49 | return 0; 50 | } 51 | 52 | [Cache.ComplexCacheAttribute.Cache( 53 | ParameterEnumArray = new EnumType[] { EnumType.Entry1 | EnumType.Entry3, EnumType.Entry2 })] 54 | public int ComplexCacheParameterMethodEnumArray() 55 | { 56 | return 0; 57 | } 58 | 59 | [Cache.ComplexCacheAttribute.Cache(ParameterBool = false, ParameterByte = Byte.MaxValue, 60 | ParameterCharacter = Char.MaxValue, ParameterDouble = Double.MaxValue, ParameterFloat = Single.MaxValue, 61 | ParameterInt = Int32.MaxValue, ParameterLong = Int64.MaxValue, ParameterSByte = SByte.MaxValue, 62 | ParameterShort = Int16.MaxValue, ParameterUInt = UInt32.MaxValue, ParameterULong = UInt64.MaxValue, 63 | ParameterUShort = UInt16.MaxValue)] 64 | public int ComplexCacheParameterMethodMax() 65 | { 66 | return 0; 67 | } 68 | 69 | [Cache.ComplexCacheAttribute.Cache(ParameterBool = false, ParameterByte = Byte.MinValue, 70 | ParameterCharacter = Char.MinValue, ParameterDouble = Double.MinValue, ParameterFloat = Single.MinValue, 71 | ParameterInt = Int32.MinValue, ParameterLong = Int64.MinValue, ParameterSByte = SByte.MinValue, 72 | ParameterShort = Int16.MinValue, ParameterUInt = UInt32.MinValue, ParameterULong = UInt64.MinValue, 73 | ParameterUShort = UInt16.MinValue)] 74 | public int ComplexCacheParameterMethodMin() 75 | { 76 | return 0; 77 | } 78 | 79 | [Cache.ComplexCacheAttribute.Cache( 80 | ParameterObjectArray = new object[] { 1, "2", EnumType.Entry3, typeof(List), new object[] { 1, "2" } })] 81 | public int ComplexCacheParameterMethodObjectArray() 82 | { 83 | return 0; 84 | } 85 | 86 | [Cache.ComplexCacheAttribute.Cache(ParameterObject = new string[] { "1", "2" })] 87 | public int ComplexCacheParameterMethodObjectArrayString() 88 | { 89 | return 0; 90 | } 91 | 92 | [Cache.ComplexCacheAttribute.Cache(ParameterObject = EnumType.Entry1 | EnumType.Entry3)] 93 | public int ComplexCacheParameterMethodObjectEnum() 94 | { 95 | return 0; 96 | } 97 | 98 | [Cache.ComplexCacheAttribute.Cache(ParameterObject = typeof(List))] 99 | public int ComplexCacheParameterMethodObjectType() 100 | { 101 | return 0; 102 | } 103 | 104 | [Cache.ComplexCacheAttribute.Cache(ParameterObject = true)] 105 | public int ComplexCacheParameterMethodObjectValueType() 106 | { 107 | return 0; 108 | } 109 | 110 | [Cache.ComplexCacheAttribute.Cache(ParameterStringArray = new[] { "1", "2" })] 111 | public int ComplexCacheParameterMethodStringArray() 112 | { 113 | return 0; 114 | } 115 | 116 | [Cache.ComplexCacheAttribute.Cache(ParameterType = typeof(List))] 117 | public int ComplexCacheParameterMethodType() 118 | { 119 | return 0; 120 | } 121 | 122 | [Cache.ComplexCacheAttribute.Cache(ParameterTypeArray = new[] { typeof(List), typeof(List) })] 123 | public int ComplexCacheParameterMethodTypeArray() 124 | { 125 | return 0; 126 | } 127 | 128 | [Cache.Cache(Parameter2 = 2, Parameter3 = true)] 129 | public string OverridenCacheParameterMethod(string argument1, int argument2) 130 | { 131 | return argument1 + argument2; 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /MethodCache.Tests/MethodCache.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {CF7379B0-EC94-4427-871E-DBD537959C09} 9 | Library 10 | Properties 11 | MethodCache.Tests 12 | MethodCache.Tests 13 | v4.0 14 | 512 15 | ..\ 16 | true 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | ..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.dll 39 | 40 | 41 | ..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.Mdb.dll 42 | 43 | 44 | ..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.Pdb.dll 45 | 46 | 47 | ..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.Rocks.dll 48 | 49 | 50 | ..\packages\NUnitTestAdapter.1.0\lib\nunit.core.dll 51 | False 52 | 53 | 54 | ..\packages\NUnitTestAdapter.1.0\lib\nunit.core.interfaces.dll 55 | False 56 | 57 | 58 | ..\packages\NUnit.2.6.3\lib\nunit.framework.dll 59 | 60 | 61 | ..\packages\NUnitTestAdapter.1.0\lib\nunit.util.dll 62 | False 63 | 64 | 65 | ..\packages\NUnitTestAdapter.1.0\lib\NUnit.VisualStudio.TestAdapter.dll 66 | False 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 | {1A9A6C8E-6B88-4FF1-9447-E37CB39AAFDE} 95 | MethodCache.Fody 96 | 97 | 98 | {CBF07605-D646-45F7-B2B0-54DC33538644} 99 | MethodCache.Tests.TestAssembly 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 114 | -------------------------------------------------------------------------------- /MethodCache.Fody/ReferenceFinder.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Fody 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Mono.Cecil; 7 | using Mono.Cecil.Rocks; 8 | 9 | public class References 10 | { 11 | public IAssemblyResolver AssemblyResolver { get; set; } 12 | 13 | public TypeDefinition CompilerGeneratedAttribute { get; set; } 14 | 15 | public MethodDefinition DebugWriteLineMethod { get; set; } 16 | 17 | public MethodReference DictionaryAddMethod { get; set; } 18 | 19 | public MethodReference DictionaryConstructor { get; set; } 20 | 21 | public GenericInstanceType DictionaryGenericInstanceType { get; set; } 22 | 23 | public TypeDefinition DictionaryInterface { get; set; } 24 | 25 | public TypeDefinition DictionaryType { get; set; } 26 | 27 | public ModuleDefinition ModuleDefinition { get; set; } 28 | 29 | public MethodDefinition StringFormatMethod { get; set; } 30 | 31 | public MethodDefinition SystemTypeGetTypeFromHandleMethod { get; set; } 32 | 33 | public TypeDefinition SystemTypeType { get; set; } 34 | 35 | public void LoadReferences() 36 | { 37 | List coreTypes = new List(); 38 | 39 | AppendTypes("System.Runtime.Extensions", coreTypes); 40 | AppendTypes("System", coreTypes); 41 | AppendTypes("mscorlib", coreTypes); 42 | AppendTypes("System.Runtime", coreTypes); 43 | AppendTypes("System.Reflection", coreTypes); 44 | 45 | TypeDefinition debugType = GetDebugType(coreTypes); 46 | 47 | DebugWriteLineMethod = 48 | debugType.Methods.First( 49 | method => 50 | method.Matches("WriteLine", ModuleDefinition.TypeSystem.Void, new[] { ModuleDefinition.TypeSystem.String })); 51 | 52 | StringFormatMethod = 53 | ModuleDefinition.TypeSystem.String.Resolve() 54 | .Methods.First( 55 | method => 56 | method.Matches("Format", ModuleDefinition.TypeSystem.String, 57 | new[] { ModuleDefinition.TypeSystem.String, ModuleDefinition.TypeSystem.Object.MakeArrayType() })); 58 | 59 | CompilerGeneratedAttribute = coreTypes.First(t => t.Name == "CompilerGeneratedAttribute"); 60 | 61 | DictionaryInterface = GetDictionaryInterface(coreTypes); 62 | 63 | DictionaryType = GetDictionaryType(coreTypes); 64 | DictionaryGenericInstanceType = DictionaryType.MakeGenericInstanceType(ModuleDefinition.TypeSystem.String, 65 | ModuleDefinition.TypeSystem.Object); 66 | 67 | DictionaryConstructor = 68 | DictionaryGenericInstanceType.Resolve() 69 | .GetConstructors() 70 | .First(x => !x.Parameters.Any()) 71 | .MakeHostInstanceGeneric(ModuleDefinition.TypeSystem.String, ModuleDefinition.TypeSystem.Object); 72 | 73 | DictionaryAddMethod = 74 | DictionaryGenericInstanceType.Resolve() 75 | .Methods.First( 76 | method => 77 | method.Matches("Add", ModuleDefinition.TypeSystem.Void, 78 | new TypeReference[] { DictionaryType.GenericParameters[0], DictionaryType.GenericParameters[1] })) 79 | .MakeHostInstanceGeneric(ModuleDefinition.TypeSystem.String, ModuleDefinition.TypeSystem.Object); 80 | 81 | SystemTypeType = GetSystemTypeType(coreTypes); 82 | 83 | SystemTypeGetTypeFromHandleMethod = 84 | SystemTypeType.Resolve() 85 | .Methods.First( 86 | method => 87 | method.Matches("GetTypeFromHandle", SystemTypeType, 88 | new TypeReference[] { GetSystemRuntimeTypeHandleType(coreTypes) })); 89 | } 90 | 91 | private void AppendTypes(string name, List coreTypes) 92 | { 93 | AssemblyDefinition definition = AssemblyResolver.Resolve(name); 94 | if (definition != null) 95 | { 96 | coreTypes.AddRange(definition.MainModule.Types); 97 | } 98 | } 99 | 100 | private TypeDefinition GetDebugType(List coreTypes) 101 | { 102 | TypeDefinition debugType = coreTypes.FirstOrDefault(x => x.Name == "Debug"); 103 | 104 | if (debugType != null) 105 | { 106 | return debugType; 107 | } 108 | 109 | AssemblyDefinition systemDiagnosticsDebug = AssemblyResolver.Resolve("System.Diagnostics.Debug"); 110 | 111 | if (systemDiagnosticsDebug != null) 112 | { 113 | debugType = systemDiagnosticsDebug.MainModule.Types.FirstOrDefault(x => x.Name == "Debug"); 114 | 115 | if (debugType != null) 116 | { 117 | return debugType; 118 | } 119 | } 120 | 121 | throw new Exception("Could not find the 'Debug' type."); 122 | } 123 | 124 | private TypeDefinition GetDictionaryInterface(List coreTypes) 125 | { 126 | TypeDefinition dictionaryType = coreTypes.FirstOrDefault(x => x.Name == "IDictionary`2"); 127 | 128 | if (dictionaryType != null) 129 | { 130 | return dictionaryType; 131 | } 132 | 133 | throw new Exception("Could not find the 'IDictionary' interface."); 134 | } 135 | 136 | private TypeDefinition GetDictionaryType(List coreTypes) 137 | { 138 | TypeDefinition dictionaryType = coreTypes.FirstOrDefault(x => x.Name == "Dictionary`2"); 139 | 140 | if (dictionaryType != null) 141 | { 142 | return dictionaryType; 143 | } 144 | 145 | throw new Exception("Could not find the 'Dictionary' type."); 146 | } 147 | 148 | private TypeDefinition GetSystemRuntimeTypeHandleType(List coreTypes) 149 | { 150 | TypeDefinition runtimeTypeHandle = coreTypes.FirstOrDefault(x => x.Name == "RuntimeTypeHandle"); 151 | 152 | if (runtimeTypeHandle != null) 153 | { 154 | return runtimeTypeHandle; 155 | } 156 | 157 | throw new Exception("Could not find the 'RuntimeHandle' type."); 158 | } 159 | 160 | private TypeDefinition GetSystemTypeType(List coreTypes) 161 | { 162 | TypeDefinition systemType = coreTypes.FirstOrDefault(x => x.Name == "Type"); 163 | 164 | if (systemType != null) 165 | { 166 | return systemType; 167 | } 168 | 169 | throw new Exception("Could not find the 'SystemType' type."); 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) 32 | 33 | 34 | 35 | 36 | $(SolutionDir).nuget 37 | packages.config 38 | 39 | 40 | 41 | 42 | $(NuGetToolsPath)\NuGet.exe 43 | @(PackageSource) 44 | 45 | "$(NuGetExePath)" 46 | mono --runtime=v4.0.30319 $(NuGetExePath) 47 | 48 | $(TargetDir.Trim('\\')) 49 | 50 | -RequireConsent 51 | -NonInteractive 52 | 53 | "$(SolutionDir) " 54 | "$(SolutionDir)" 55 | 56 | 57 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 58 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 59 | 60 | 61 | 62 | RestorePackages; 63 | $(BuildDependsOn); 64 | 65 | 66 | 67 | 68 | $(BuildDependsOn); 69 | BuildPackage; 70 | 71 | 72 | 73 | 74 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /Settings.StyleCop: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | False 8 | 9 | 10 | 11 | 12 | False 13 | 14 | 15 | 16 | 17 | False 18 | 19 | 20 | 21 | 22 | False 23 | 24 | 25 | 26 | 27 | False 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | False 38 | 39 | 40 | 41 | 42 | False 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | True 53 | 54 | 55 | 56 | 57 | False 58 | 59 | 60 | 61 | 62 | False 63 | 64 | 65 | 66 | 67 | False 68 | 69 | 70 | 71 | 72 | False 73 | 74 | 75 | 76 | 77 | False 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | False 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | False 98 | 99 | 100 | 101 | 102 | $(AaBb) 103 | $(AaBb) 104 | $(aaBb) 105 | $(aaBb) 106 | $(aaBb) 107 | $(aaBb) 108 | $(AaBb) 109 | $(AaBb) 110 | $(AaBb) 111 | NotEmpty 112 | $(AaBb):$(AaBb)_$(AaBb) 113 | 3D A B C FxCop ID IDs IL OK PE StyleCop X 114 | 115 | 116 | 117 | 118 | 119 | 120 | False 121 | 122 | 123 | 124 | 125 | False 126 | 127 | 128 | 129 | 130 | False 131 | 132 | 133 | 134 | 135 | False 136 | 137 | 138 | 139 | 140 | False 141 | 142 | 143 | 144 | 145 | False 146 | 147 | 148 | 149 | 150 | False 151 | 152 | 153 | 154 | 155 | False 156 | 157 | 158 | 159 | 160 | False 161 | 162 | 163 | 164 | 165 | False 166 | 167 | 168 | 169 | 170 | False 171 | 172 | 173 | 174 | 175 | False 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Migrated to SpatialFocus.MethodCache 2 | 3 | We have made a complete rewrite of MethodCache that supports latest version of Fody . Please find the new version [here](https://github.com/SpatialFocus/MethodCache.Fody/). 4 | 5 | ---- 6 | 7 | ## This is an add-in for [Fody](https://github.com/Fody/Fody/) 8 | 9 | Caches return values of methods and properties decorated with a `[Cache]` Attribute. 10 | 11 | [Introduction to Fody](http://github.com/Fody/Fody/wiki/SampleUsage). 12 | 13 | # Nuget package 14 | 15 | There is a nuget package avaliable here http://nuget.org/packages/MethodCache.Fody. 16 | 17 | ## Your Code 18 | 19 | [Cache] 20 | public int Add(int a, int b) 21 | { 22 | return a + b; 23 | } 24 | 25 | [Cache] 26 | public string AlsoWorksForProperties 27 | { 28 | get 29 | { 30 | return DoSomeCalculations(this.parameterField); 31 | } 32 | set 33 | { 34 | this.parameterField = value; 35 | } 36 | } 37 | 38 | ## What gets compiled 39 | 40 | public int Add(int a, int b) 41 | { 42 | string cacheKey = string.Format("Namespace.Class.Add_{0}_{1}", new object[] { a, b }); 43 | 44 | if(Cache.Contains(cacheKey)) 45 | { 46 | return Cache.Retrieve(cacheKey); 47 | } 48 | 49 | int result = a + b; 50 | 51 | Cache.Store(cacheKey, result); 52 | 53 | return result; 54 | } 55 | 56 | public object AlsoWorksForProperties 57 | { 58 | get 59 | { 60 | string cacheKey = "Namespace.Class.AlsoWorksForProperties"; 61 | 62 | if(Cache.Contains(cacheKey)) 63 | { 64 | return Cache.Retrieve(cacheKey); 65 | } 66 | 67 | object result = DoSomeCalculations(this.parameterField); 68 | 69 | Cache.Store(cacheKey, result); 70 | 71 | return result; 72 | } 73 | set 74 | { 75 | string cacheKey = "Namespace.Class.AlsoWorksForProperties"; 76 | Cache.Remove(cacheKey); 77 | this.parameterField = value; 78 | } 79 | } 80 | 81 | # How to use 82 | 83 | * Install MethodCache.Fody via Nuget 84 | * Create an Cache Implementation (MemCache, FileCache, DBCache, ...) which implements Contains, Retrieve, Store and Remove methods. 85 | 86 | Optional 87 | 88 | * Add your own CacheAttribute and NoCacheAttribute to your Solution to decorate your methods or classes (you can use the existing attributes defined in MethodCache.Attributes). 89 | 90 | ## Example 91 | 92 | DictionaryCache (in-memory implementation): 93 | 94 | public class DictionaryCache 95 | { 96 | public DictionaryCache() 97 | { 98 | Storage = new Dictionary(); 99 | } 100 | 101 | private Dictionary Storage { get; set; } 102 | 103 | // Note: The methods Contains, Retrieve, Store (and Remove) must exactly look like the following: 104 | 105 | public bool Contains(string key) 106 | { 107 | return Storage.ContainsKey(key); 108 | } 109 | 110 | public T Retrieve(string key) 111 | { 112 | return (T)Storage[key]; 113 | } 114 | 115 | public void Store(string key, object data) 116 | { 117 | Storage[key] = data; 118 | } 119 | 120 | // Remove is needed for writeable properties which must invalidate the Cache 121 | // You can skip this method but then only readonly properties are supported 122 | public void Remove(string key) 123 | { 124 | Storage.Remove(key); 125 | } 126 | } 127 | 128 | Now all the preparation is done and you can start with the real work. The classes you want to cache must contain an Cache Getter (can also be inherited from a baseclass). Let's start decorating ... 129 | 130 | // Mark the class to enable caching of every method ... 131 | 132 | [Cache] 133 | public class ClassToCache 134 | { 135 | public ClassToCache() 136 | { 137 | // Consider using constructor or property injection instead 138 | Cache = new DictionaryCache(); 139 | } 140 | 141 | // Consider using ICache Interface 142 | private DictionaryCache Cache { get; set; } 143 | 144 | // This method will be cached 145 | public int Add(int a, int b) 146 | { 147 | return a + b; 148 | } 149 | 150 | // This method will be cached too 151 | public string Concat(string a, string b) 152 | { 153 | return a + b; 154 | } 155 | 156 | // This method will not be cached 157 | [NoCache] 158 | public int ThirdMethod(int x) 159 | { 160 | return x * x; 161 | } 162 | 163 | public string AlsoWorksForProperties 164 | { 165 | get 166 | { 167 | return DoSomeCalculations(); 168 | } 169 | } 170 | 171 | [NoCache] 172 | public string AlsoPropertiesCanBeIgnored 173 | { 174 | get 175 | { 176 | return 10; 177 | } 178 | } 179 | } 180 | 181 | // or mark the methods you want to cache explicitly. 182 | 183 | public class ClassToCache 184 | { 185 | public ClassToCache(ICache cache) 186 | { 187 | // Consider using constructor or property injection instead 188 | Cache = new DictionaryCache(); 189 | } 190 | 191 | private ICache Cache { get; set; } 192 | 193 | [Cache] // Only this method ... 194 | public int Add(int a, int b) 195 | { 196 | return a + b; 197 | } 198 | 199 | public string Concat(string a, string b) 200 | { 201 | return a + b; 202 | } 203 | 204 | public int ThirdMethod(int x) 205 | { 206 | return x * x; 207 | } 208 | 209 | [Cache] // ... and this properties will be cached 210 | public string AlsoWorksForProperties 211 | { 212 | get 213 | { 214 | return DoSomeCalculations(); 215 | } 216 | } 217 | } 218 | 219 | ... and let MethodCache do the rest. 220 | 221 | ## CacheAttribute Parameters Support (since 1.5) 222 | 223 | For fine-grained cache control you can use a custom `CacheAttribute` and add properties or fields which you can set on class or method level. This parameters will be passed to the Store function. 224 | 225 | See the following example which uses the `System.Runtime.Caching.ObjectCache` in combination with `System.Runtime.Caching.CacheItemPolicy` and a custom `CacheAttribute` with a property named Duration to control the cache duration. 226 | 227 | // Use a custom CacheAttribute 228 | 229 | public class CacheAttribute : Attribute 230 | { 231 | // You can use all types that are valid Attribute parameter types 232 | public int Duration { get; set; } 233 | } 234 | 235 | // Decorate you classes 236 | 237 | public class ClassToCache 238 | { 239 | public ClassToCache(ICache cache) 240 | { 241 | Cache = cache; 242 | } 243 | 244 | protected ICache Cache { get; set; } 245 | 246 | // Note: Only works when set as named parameter 247 | [Cache(Duration = 5000)] 248 | public int ShortTermFunction() 249 | { 250 | return 0; 251 | } 252 | 253 | [Cache] 254 | public int LongTermFunction() 255 | { 256 | return 0; 257 | } 258 | } 259 | 260 | // Use the new void Store(string key, object data, IDictionary parameters) signature 261 | 262 | public class ObjectCacheAdapter 263 | { 264 | public ObjectCacheAdapter(ObjectCache objectCache) 265 | { 266 | ObjectCache = objectCache; 267 | } 268 | 269 | protected ObjectCache ObjectCache { get; set; } 270 | 271 | // Note: The methods Contains, Retrieve, Store (and Remove) must exactly look like the following: 272 | public bool Contains(string key) 273 | { 274 | return ObjectCache.Contains(key); 275 | } 276 | 277 | public T Retrieve(string key) 278 | { 279 | return (T) ObjectCache.GetCacheItem(key).Value; 280 | } 281 | 282 | public void Store(string key, object data, IDictionary parameters) 283 | { 284 | CacheItemPolicy cacheItemPolicy = null; 285 | 286 | // Create a CacheItemPolicy instance if Duration is set 287 | if (parameters.ContainsKey("Duration")) 288 | { 289 | cacheItemPolicy = new CacheItemPolicy() 290 | { 291 | AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds((int)parameters["Duration"]) 292 | }; 293 | } 294 | 295 | ObjectCache.Add(key, data, cacheItemPolicy); 296 | } 297 | 298 | // Remove is needed for writeable properties which must invalidate the Cache 299 | // You can skip this method but then only readonly properties are supported 300 | public void Remove(string key) 301 | { 302 | ObjectCache.Remove(key); 303 | } 304 | } 305 | 306 | Class level named attribute parameters are overriden by method level attribute parameters (even if not set): 307 | 308 | [Cache.Cache(Parameter1 = "CacheParameter", Parameter2 = 1)] 309 | public class TestClassWithParameterizedCacheAttribute 310 | { 311 | // Store calls for this method will contain Parameter1 and Parameter2 312 | public int SomeMethod() 313 | { 314 | return 0; 315 | } 316 | 317 | // Store calls for this method will contain Parameter2 and Parameter3 318 | [Cache.Cache(Parameter2 = 2, Parameter3 = true)] 319 | public int SomeOverriddenMethod() 320 | { 321 | return 0; 322 | } 323 | } 324 | 325 | ## Miscellaneous 326 | 327 | ### Choosing what to cache 328 | 329 | Class' methods and properties are selected for caching using the following algorithm: 330 | 331 | 1. For each member 332 | 1. If it is marked with `[Cache]` attribute, **cache it** 333 | 2. If it is marked with `[NoCache]` attribute, **don't cache it** 334 | 2. If it is a method 335 | 2. If it the class is marked with `[Cache(Members.Methods)]` or `[Cache(Members.All)]`, **cache it** 336 | 3. It it the class is marked with `[Cache]` attribute and methods are not disabled in XML config, **cache it** 337 | 3. If it is a property 338 | 1. If it is an auto-property, **don't cache it** 339 | 2. If it is a write-only property, **don't cache it** 340 | 3. If it is the `Cache` property, **don't cache it** 341 | 2. If it the class is marked with `[Cache(Members.Properties)]` or `[Cache(Members.All)]`, **cache it** 342 | 3. It it the class is marked with `[Cache]` attribute and properties are not disabled in XML config, **cache it** 343 | 4. Otherwise, **don't cache it** 344 | 345 | For disabling method or property caching per default (say you only want to cache methods when using `[Cache]` on class level) edit the XML settings in ModuleWeavers.xml: 346 | 347 | `` 348 | 349 | Notes 350 | * `[Cache(Members.Methods | Members.Properties)]` can be used in place of `[Cache(Members.All)]`. 351 | * Arguments passed to `[Cache]` attribute will be ignored on individual members 352 | 353 | ### Improvements 354 | 355 | For production I would suggest using some DI framework and creating an ICache interface: 356 | 357 | public interface ICache 358 | { 359 | bool Contains(string key); 360 | 361 | T Retrieve(string key); 362 | 363 | void Store(string key, object data); 364 | 365 | // For CacheAttribute parameter support use the following 366 | // void Store(string key, object data, IDictionary parameters) 367 | 368 | void Remove(string key); 369 | } 370 | 371 | public class MyService 372 | { 373 | // Constructor injection 374 | public MyService(ICache cache) 375 | { 376 | Cache = cache; 377 | } 378 | 379 | protected ICache Cache { get; set; } 380 | } 381 | 382 | ### Runtime Debug messages 383 | 384 | When compiled in Debug mode, MethodCache outputs Cache information with Debug.WriteLine: 385 | 386 | CacheKey created: MethodCache.TestAssembly.TestClass1.MethodOne_1337 387 | Storing to cache. 388 | CacheKey created: MethodCache.TestAssembly.TestClass1.MethodOne_1337 389 | Loading from cache. 390 | ... 391 | 392 | If you do not like this behaviour (e.g. if lots of messages slow down the application), add SkipDebugOutput="false" to ModuleWeavers.xml. 393 | 394 | ### Enable Weaving Build Messages 395 | 396 | Be default, only warnings like a missing Cache Getter are shown in the build log. To enable detailed information, modify the following line in Fody.targets 397 | 398 | From 399 | 400 | Low 401 | 402 | To 403 | 404 | High 405 | 406 | You will now see detailed weaving information in the build log: 407 | 408 | Searching for Methods in assembly (MethodCache.TestAssembly.dll). 409 | Weaving method TestClassOne::MethodOne. 410 | Checking CacheType methods (Contains, Store, Retrieve). 411 | CacheType methods found. 412 | ... 413 | 414 | ### Credits 415 | 416 | Thanks to [Tomasz Pluskiewicz](https://github.com/tpluscode) for his contribution to support caching of properties. 417 | -------------------------------------------------------------------------------- /MethodCache.Tests/ModuleWeaverTests.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests 2 | { 3 | using System; 4 | using System.Linq; 5 | using System.Reflection; 6 | using MethodCache.Fody; 7 | using MethodCache.Tests.TestAssembly; 8 | using MethodCache.Tests.TestAssembly.Cache; 9 | using NUnit.Framework; 10 | 11 | public class ModuleWeaverTests : ModuleWeaverTestsBase 12 | { 13 | [Test] 14 | public void ClassLevelCacheMethodsExcluded() 15 | { 16 | // Arrange 17 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 18 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 19 | 20 | // Act 21 | dynamic value = instance.Method(10); 22 | value = instance.Method(10); 23 | 24 | // Assert 25 | Assert.IsTrue(cache.NumStoreCalls == 0); 26 | Assert.IsTrue(cache.NumRetrieveCalls == 0); 27 | } 28 | 29 | [Test] 30 | public void ClassLevelCacheMethodsExcludedChooseSelectively() 31 | { 32 | // Arrange 33 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 34 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 35 | 36 | // Act 37 | dynamic value = instance.MethodIncluded(10); 38 | value = instance.MethodIncluded(10); 39 | 40 | // Assert 41 | Assert.IsTrue(cache.NumStoreCalls == 1); 42 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 43 | } 44 | 45 | [Test] 46 | public void ClassLevelCacheMethodsWhenPropertiesExcluded() 47 | { 48 | // Arrange 49 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 50 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 51 | 52 | // Act 53 | dynamic value = instance.Method(10); 54 | value = instance.Method(10); 55 | 56 | // Assert 57 | Assert.IsTrue(cache.NumStoreCalls == 1); 58 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 59 | } 60 | 61 | [Test] 62 | public void ModuleWeaverExecuteWeavesCorrectIL() 63 | { 64 | string assemblyPath = Assembly.CodeBase.Remove(0, 8); 65 | string result = Verifier.Verify(assemblyPath); 66 | 67 | Assert.IsTrue(result.Contains(string.Format("All Classes and Methods in {0} Verified.", assemblyPath))); 68 | } 69 | 70 | [Test] 71 | public void ModuleWeaverRemovesMethodCacheAttributeReference() 72 | { 73 | Assert.IsFalse(Assembly.GetReferencedAssemblies().Any(x => x.Name == "MethodCache.Attributes")); 74 | } 75 | 76 | [Test] 77 | public void TestClass1ClassLevelCacheAttributeCachedMethodTwoReturnsSameInstance() 78 | { 79 | // Arrange 80 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 81 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 82 | 83 | // Act 84 | dynamic instance1 = testClass1.MethodTwo("1337"); 85 | dynamic instance2 = testClass1.MethodTwo("1337"); 86 | 87 | // Assert 88 | Assert.IsTrue(ReferenceEquals(instance1, instance2)); 89 | } 90 | 91 | [Test] 92 | public void TestClass1ClassLevelCacheAttributeCachesMethodOne() 93 | { 94 | // Arrange 95 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 96 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 97 | 98 | // Act 99 | testClass1.MethodOne(1337); 100 | testClass1.MethodOne(1337); 101 | 102 | // Assert 103 | Assert.IsTrue(cache.NumStoreCalls == 1); 104 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 105 | } 106 | 107 | [Test] 108 | public void TestClass1ClassLevelCacheAttributeCachesMethodTwo() 109 | { 110 | // Arrange 111 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 112 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 113 | 114 | // Act 115 | testClass1.MethodTwo("1337"); 116 | testClass1.MethodTwo("1337"); 117 | 118 | // Assert 119 | Assert.IsTrue(cache.NumStoreCalls == 1); 120 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 121 | } 122 | 123 | [Test] 124 | public void TestClass1ClassLevelCacheAttributeSuccessfullyRemoved() 125 | { 126 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 127 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 128 | 129 | Type classType = testClass1.GetType(); 130 | 131 | Assert.IsFalse(classType.GetCustomAttributes(true).Any(x => x.GetType().Name == ModuleWeaver.CacheAttributeName)); 132 | } 133 | 134 | [Test] 135 | public void TestClass2MethodLevelCacheAttributeCachesMethodOne() 136 | { 137 | // Arrange 138 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 139 | dynamic testClass2 = WeaverHelper.CreateInstance(Assembly, cache); 140 | 141 | // Act 142 | testClass2.MethodOne(1337); 143 | testClass2.MethodOne(1337); 144 | 145 | // Assert 146 | Assert.IsTrue(cache.NumStoreCalls == 1); 147 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 148 | } 149 | 150 | [Test] 151 | public void TestClass2MethodLevelCacheAttributeDoesNotCachesMethodTwo() 152 | { 153 | // Arrange 154 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 155 | dynamic testClass2 = WeaverHelper.CreateInstance(Assembly, cache); 156 | 157 | // Act 158 | testClass2.MethodTwo("1337"); 159 | testClass2.MethodTwo("1337"); 160 | 161 | // Assert 162 | Assert.IsTrue(cache.NumStoreCalls == 0); 163 | Assert.IsTrue(cache.NumRetrieveCalls == 0); 164 | } 165 | 166 | [Test] 167 | public void TestClass2MethodLevelCacheAttributeNotCachedMethodTwoReturnsDifferentInstances() 168 | { 169 | // Arrange 170 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 171 | dynamic testClass2 = WeaverHelper.CreateInstance(Assembly, cache); 172 | 173 | // Act 174 | dynamic instance1 = testClass2.MethodTwo("1337"); 175 | dynamic instance2 = testClass2.MethodTwo("1337"); 176 | 177 | // Assert 178 | Assert.IsTrue(!ReferenceEquals(instance1, instance2)); 179 | } 180 | 181 | [Test] 182 | public void TestClass2MethodLevelCacheAttributeSuccessfullyRemoved() 183 | { 184 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 185 | dynamic testClass2 = WeaverHelper.CreateInstance(Assembly, cache); 186 | 187 | Type classType = testClass2.GetType(); 188 | MethodInfo methodInfo = classType.GetMethod("MethodOne"); 189 | 190 | Assert.IsFalse(methodInfo.GetCustomAttributes(true).Any(x => x.GetType().Name == ModuleWeaver.CacheAttributeName)); 191 | } 192 | 193 | [Test] 194 | public void TestClass3ConcreteCacheGetterCachesMethodOne() 195 | { 196 | // Arrange 197 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 198 | dynamic testClass2 = WeaverHelper.CreateInstance(Assembly, cache); 199 | 200 | // Act 201 | testClass2.MethodOne(1337); 202 | testClass2.MethodOne(1337); 203 | 204 | // Assert 205 | Assert.IsTrue(cache.NumStoreCalls == 1); 206 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 207 | } 208 | 209 | [Test] 210 | public void TestClass3ConcreteCacheGetterCachesMethodTwo() 211 | { 212 | // Arrange 213 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 214 | dynamic testClass2 = WeaverHelper.CreateInstance(Assembly, cache); 215 | 216 | // Act 217 | testClass2.MethodTwo("1337"); 218 | testClass2.MethodTwo("1337"); 219 | 220 | // Assert 221 | Assert.IsTrue(cache.NumStoreCalls == 1); 222 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 223 | } 224 | 225 | [Test] 226 | public void TestClass4MissingCacheGetterSuccessfullyIgnored() 227 | { 228 | // Arrange 229 | dynamic testClass4 = WeaverHelper.CreateInstance(Assembly); 230 | 231 | // Act 232 | testClass4.MethodOne(1337); 233 | 234 | // Assert (test should not throw any exceptions) 235 | } 236 | 237 | [Test] 238 | public void TestClass5WrongCacheGetterTypeSuccessfullyIgnored() 239 | { 240 | // Arrange 241 | dynamic testClass5 = WeaverHelper.CreateInstance(Assembly); 242 | 243 | // Act 244 | testClass5.MethodOne(1337); 245 | 246 | // Assert (test should not throw any exceptions) 247 | } 248 | 249 | [Test] 250 | public void TestClass6InheritedCacheGetterCanBeUsed() 251 | { 252 | // Arrange 253 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 254 | dynamic testClass6 = WeaverHelper.CreateInstance(Assembly, cache); 255 | 256 | // Act 257 | testClass6.MethodThree(1337); 258 | testClass6.MethodThree(1337); 259 | 260 | // Assert 261 | Assert.IsTrue(cache.NumStoreCalls == 1); 262 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 263 | } 264 | 265 | [Test] 266 | public void TestClass7MethodLevelNoCacheAttributeDoesNotCachesMethodTwo() 267 | { 268 | // Arrange 269 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 270 | dynamic testClass7 = WeaverHelper.CreateInstance(Assembly, cache); 271 | 272 | // Act 273 | testClass7.MethodTwo("1337"); 274 | testClass7.MethodTwo("1337"); 275 | 276 | // Assert 277 | Assert.IsTrue(cache.NumStoreCalls == 0); 278 | Assert.IsTrue(cache.NumRetrieveCalls == 0); 279 | } 280 | 281 | [Test] 282 | public void TestClass7MethodLevelNoCacheAttributeNotCachedMethodTwoReturnsDifferentInstances() 283 | { 284 | // Arrange 285 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 286 | dynamic testClass7 = WeaverHelper.CreateInstance(Assembly, cache); 287 | 288 | // Act 289 | dynamic instance1 = testClass7.MethodTwo("1337"); 290 | dynamic instance2 = testClass7.MethodTwo("1337"); 291 | 292 | // Assert 293 | Assert.IsTrue(!ReferenceEquals(instance1, instance2)); 294 | } 295 | 296 | [Test] 297 | public void TestClass7MethodLevelNoCacheAttributeSuccessfullyRemoved() 298 | { 299 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 300 | dynamic testClass7 = WeaverHelper.CreateInstance(Assembly, cache); 301 | 302 | Type classType = testClass7.GetType(); 303 | MethodInfo methodInfo = classType.GetMethod("MethodTwo"); 304 | 305 | Assert.IsFalse(methodInfo.GetCustomAttributes(true).Any(x => x.GetType().Name == ModuleWeaver.NoCacheAttributeName)); 306 | } 307 | 308 | [Test] 309 | public void TestClass8ClassLevelCacheAttributeCachedMethodTwoReturnsSameInstance() 310 | { 311 | // Arrange 312 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 313 | 314 | Type testClassType = Assembly.GetType(typeof(TestClass8).FullName); 315 | Type cacheType = Assembly.GetType(typeof(ICache).FullName); 316 | 317 | PropertyInfo cacheProperty = testClassType.GetProperty("Cache", cacheType); 318 | cacheProperty.SetValue(null, cache, null); 319 | 320 | MethodInfo method = testClassType.GetMethod("MethodTwo", new[] { typeof(string) }); 321 | 322 | // Act 323 | dynamic instance1 = method.Invoke(null, new object[] { "1337" }); 324 | dynamic instance2 = method.Invoke(null, new object[] { "1337" }); 325 | 326 | // Assert 327 | Assert.IsTrue(ReferenceEquals(instance1, instance2)); 328 | } 329 | 330 | [Test] 331 | public void TestClass8ClassLevelCacheAttributeCachesMethodOne() 332 | { 333 | // Arrange 334 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 335 | 336 | Type testClassType = Assembly.GetType(typeof(TestClass8).FullName); 337 | Type cacheType = Assembly.GetType(typeof(ICache).FullName); 338 | 339 | PropertyInfo cacheProperty = testClassType.GetProperty("Cache", cacheType); 340 | cacheProperty.SetValue(null, cache, null); 341 | 342 | MethodInfo method = testClassType.GetMethod("MethodOne", new[] { typeof(int) }); 343 | 344 | // Act 345 | method.Invoke(null, new object[] { 1337 }); 346 | method.Invoke(null, new object[] { 1337 }); 347 | 348 | // Assert 349 | Assert.IsTrue(cache.NumStoreCalls == 1); 350 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 351 | } 352 | 353 | [Test] 354 | public void TestClass8ClassLevelCacheAttributeCachesMethodTwo() 355 | { 356 | // Arrange 357 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 358 | 359 | Type testClassType = Assembly.GetType(typeof(TestClass8).FullName); 360 | Type cacheType = Assembly.GetType(typeof(ICache).FullName); 361 | 362 | PropertyInfo cacheProperty = testClassType.GetProperty("Cache", cacheType); 363 | cacheProperty.SetValue(null, cache, null); 364 | 365 | MethodInfo method = testClassType.GetMethod("MethodTwo", new[] { typeof(string) }); 366 | 367 | // Act 368 | method.Invoke(null, new object[] { "1337" }); 369 | method.Invoke(null, new object[] { "1337" }); 370 | 371 | // Assert 372 | Assert.IsTrue(cache.NumStoreCalls == 1); 373 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 374 | } 375 | } 376 | } -------------------------------------------------------------------------------- /MethodCache.Tests/PropertyCachingTests.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests 2 | { 3 | using System; 4 | using System.Reflection; 5 | using MethodCache.Tests.TestAssembly; 6 | using MethodCache.Tests.TestAssembly.Cache; 7 | using NUnit.Framework; 8 | 9 | public class PropertyCachingTests : ModuleWeaverTestsBase 10 | { 11 | [Test] 12 | public void ClassLevelCacheWithoutRemove_ShouldCacheReadOnlyProperties() 13 | { 14 | // Arrange 15 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 16 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 17 | 18 | // Act 19 | dynamic value = instance.ReadOnlyProperty; 20 | value = instance.ReadOnlyProperty; 21 | 22 | // Assert 23 | Assert.IsTrue(cache.NumStoreCalls == 1); 24 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 25 | } 26 | 27 | [Test] 28 | public void ClassLevelCacheWithoutRemove_ShouldNotCacheReadWriteProperty() 29 | { 30 | // Arrange 31 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 32 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 33 | 34 | // Act 35 | dynamic value = instance.ReadWriteProperty; 36 | value = instance.ReadWriteProperty; 37 | 38 | // Assert 39 | Assert.IsTrue(cache.NumStoreCalls == 0); 40 | Assert.IsTrue(cache.NumRetrieveCalls == 0); 41 | } 42 | 43 | [Test] 44 | public void ClassLevelCache_SettingPropertyShouldInvalidateCache() 45 | { 46 | // Arrange 47 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 48 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 49 | 50 | // Act 51 | dynamic value = instance.ReadWriteProperty; 52 | instance.ReadWriteProperty = 10; 53 | 54 | // Assert 55 | Assert.That(cache.Contains("MethodCache.Tests.TestAssembly.TestClassWithProperties.ReadWriteProperty"), Is.False); 56 | } 57 | 58 | [Test] 59 | public void ClassLevelCache_ShouldBePossibleToDisablePropertyWeaving() 60 | { 61 | // Arrange 62 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 63 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 64 | 65 | // Act 66 | dynamic value = instance.ReadOnlyProperty; 67 | value = instance.ReadOnlyProperty; 68 | 69 | // Assert 70 | Assert.IsTrue(cache.NumStoreCalls == 0); 71 | Assert.IsTrue(cache.NumRetrieveCalls == 0); 72 | } 73 | 74 | [Test] 75 | public void ClassLevelCache_ShouldBePossibleToSelectivelyEnablePropertyWeaving() 76 | { 77 | // Arrange 78 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 79 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 80 | 81 | // Act 82 | dynamic value = instance.ExplicitlyCachedProperty; 83 | value = instance.ExplicitlyCachedProperty; 84 | 85 | // Assert 86 | Assert.IsTrue(cache.NumStoreCalls == 1); 87 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 88 | } 89 | 90 | [Test] 91 | public void ClassLevelCache_ShouldCachePropertyWhenMethodsAreExcluded() 92 | { 93 | // Arrange 94 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 95 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 96 | 97 | // Act 98 | dynamic value = instance.Property; 99 | value = instance.Property; 100 | 101 | // Assert 102 | Assert.IsTrue(cache.NumStoreCalls == 1); 103 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 104 | } 105 | 106 | [Test] 107 | public void ClassLevelCache_ShouldCacheReadOnlyProperties() 108 | { 109 | // Arrange 110 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 111 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 112 | 113 | // Act 114 | dynamic value = instance.ReadOnlyProperty; 115 | value = instance.ReadOnlyProperty; 116 | 117 | // Assert 118 | Assert.IsTrue(cache.NumStoreCalls == 1); 119 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 120 | } 121 | 122 | [Test] 123 | public void ClassLevelCache_ShouldCacheReadWriteProperty() 124 | { 125 | // Arrange 126 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 127 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 128 | 129 | // Act 130 | dynamic value = instance.ReadWriteProperty; 131 | value = instance.ReadWriteProperty; 132 | 133 | // Assert 134 | Assert.IsTrue(cache.NumStoreCalls == 1); 135 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 136 | } 137 | 138 | [Test] 139 | public void ClassLevelCache_ShouldCacheStaticReadOnlyProperties() 140 | { 141 | // Arrange 142 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 143 | 144 | Type testClassType = Assembly.GetType(typeof(TestClassStaticProperties).FullName); 145 | Type cacheType = Assembly.GetType(typeof(ICacheWithRemove).FullName); 146 | 147 | PropertyInfo cacheProperty = testClassType.GetProperty("Cache", cacheType); 148 | cacheProperty.SetValue(null, cache, null); 149 | 150 | PropertyInfo property = testClassType.GetProperty("ReadOnlyProperty"); 151 | 152 | // Act 153 | object value = property.GetValue(null, null); 154 | value = property.GetValue(null, null); 155 | 156 | // Assert 157 | Assert.IsTrue(cache.NumStoreCalls == 1); 158 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 159 | } 160 | 161 | [Test] 162 | public void ClassLevelCache_ShouldCacheStaticReadWriteProperties() 163 | { 164 | // Arrange 165 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 166 | 167 | Type testClassType = Assembly.GetType(typeof(TestClassStaticProperties).FullName); 168 | Type cacheType = Assembly.GetType(typeof(ICacheWithRemove).FullName); 169 | 170 | PropertyInfo cacheProperty = testClassType.GetProperty("Cache", cacheType); 171 | cacheProperty.SetValue(null, cache, null); 172 | 173 | PropertyInfo property = testClassType.GetProperty("ReadWriteProperty"); 174 | 175 | // Act 176 | object value = property.GetValue(null, null); 177 | value = property.GetValue(null, null); 178 | 179 | // Assert 180 | Assert.IsTrue(cache.NumStoreCalls == 1); 181 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 182 | } 183 | 184 | [Test] 185 | public void ClassLevelCache_ShouldInvalidateCacheWhenSettingReadWriteProperties() 186 | { 187 | // Arrange 188 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 189 | 190 | Type testClassType = Assembly.GetType(typeof(TestClassStaticProperties).FullName); 191 | Type cacheType = Assembly.GetType(typeof(ICacheWithRemove).FullName); 192 | 193 | PropertyInfo cacheProperty = testClassType.GetProperty("Cache", cacheType); 194 | cacheProperty.SetValue(null, cache, null); 195 | 196 | PropertyInfo property = testClassType.GetProperty("ReadWriteProperty"); 197 | 198 | // Act 199 | property.GetValue(null, null); 200 | property.SetValue(null, 5, null); 201 | 202 | // Assert 203 | Assert.That(cache.Contains("MethodCache.Tests.TestAssembly.TestClassStaticProperties.ReadWriteProperty"), Is.False); 204 | } 205 | 206 | [Test] 207 | public void ClassLevelCache_ShouldNotCacheAutoProperties() 208 | { 209 | // Arrange 210 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 211 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 212 | 213 | // Act 214 | dynamic value = instance.AutoProperty; 215 | value = instance.AutoProperty; 216 | 217 | // Assert 218 | Assert.IsTrue(cache.NumStoreCalls == 0); 219 | Assert.IsTrue(cache.NumRetrieveCalls == 0); 220 | } 221 | 222 | [Test] 223 | public void ClassLevelCache_ShouldNotCacheReadOnlyAutoProperties() 224 | { 225 | // Arrange 226 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 227 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 228 | 229 | // Act 230 | dynamic value = instance.ReadOnlyAutoProperty; 231 | value = instance.ReadOnlyAutoProperty; 232 | 233 | // Assert 234 | Assert.IsTrue(cache.NumStoreCalls == 0); 235 | Assert.IsTrue(cache.NumRetrieveCalls == 0); 236 | } 237 | 238 | [Test] 239 | public void ClassLevelCache_ShouldNotWeaveWriteOnlyProperties() 240 | { 241 | // Arrange 242 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 243 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 244 | 245 | // Act 246 | instance.SetOnlyProperty = "some text"; 247 | 248 | // Assert 249 | Assert.That(cache.NumRemoveCalls, Is.EqualTo(0)); 250 | } 251 | 252 | [Test] 253 | public void ClassLevelCache_ShouldRemoveCacheAttributesFromAutoproperties() 254 | { 255 | // Arrange 256 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 257 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 258 | Type type = instance.GetType(); 259 | PropertyInfo property = type.GetProperty("AutoProperty"); 260 | 261 | // Act 262 | object[] customAttributes = property.GetCustomAttributes(true); 263 | 264 | // Assert 265 | Assert.IsEmpty(customAttributes); 266 | } 267 | 268 | [Test] 269 | public void ClassLevelCache_ShouldRemoveClassCacheAttribute() 270 | { 271 | // Arrange 272 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 273 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 274 | Type type = instance.GetType(); 275 | 276 | // Act 277 | object[] customAttributes = type.GetCustomAttributes(true); 278 | 279 | // Assert 280 | Assert.IsEmpty(customAttributes); 281 | } 282 | 283 | [Test] 284 | public void ClassLevelCache_ShouldRemoveNoCacheAttribute() 285 | { 286 | // Arrange 287 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 288 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 289 | Type type = instance.GetType(); 290 | PropertyInfo property = type.GetProperty("ReadOnlyNoCache"); 291 | 292 | // Act 293 | object[] customAttributes = property.GetCustomAttributes(true); 294 | 295 | // Assert 296 | Assert.IsEmpty(customAttributes); 297 | } 298 | 299 | [Test] 300 | public void ClassLevelCache_ShouldRespectNoCacheAttribute() 301 | { 302 | // Arrange 303 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 304 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 305 | 306 | // Act 307 | dynamic value = instance.ReadOnlyNoCache; 308 | value = instance.ReadOnlyNoCache; 309 | 310 | // Assert 311 | Assert.IsTrue(cache.NumStoreCalls == 0); 312 | Assert.IsTrue(cache.NumRetrieveCalls == 0); 313 | } 314 | 315 | [Test] 316 | public void IndividualCache_SettingPropertyShouldInvalidateCache() 317 | { 318 | // Arrange 319 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 320 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 321 | 322 | // Act 323 | dynamic value = instance.ReadWriteProperty; 324 | instance.ReadWriteProperty = 10; 325 | 326 | // Assert 327 | Assert.That(cache.Contains("MethodCache.Tests.TestAssembly.TestClassIndividualProperties.ReadWriteProperty"), 328 | Is.False); 329 | } 330 | 331 | [Test] 332 | public void IndividualCache_ShouldCacheReadOnlyProperty() 333 | { 334 | // Arrange 335 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 336 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 337 | 338 | // Act 339 | dynamic value = instance.ReadOnlyProperty; 340 | value = instance.ReadOnlyProperty; 341 | 342 | // Assert 343 | Assert.IsTrue(cache.NumStoreCalls == 1); 344 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 345 | } 346 | 347 | [Test] 348 | public void IndividualCache_ShouldCacheReadWriteProperty() 349 | { 350 | // Arrange 351 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 352 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 353 | 354 | // Act 355 | dynamic value = instance.ReadWriteProperty; 356 | value = instance.ReadWriteProperty; 357 | 358 | // Assert 359 | Assert.IsTrue(cache.NumStoreCalls == 1); 360 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 361 | } 362 | 363 | [Test] 364 | public void IndividualCache_ShouldNeverWeaveWriteOnlyProperties() 365 | { 366 | // Arrange 367 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 368 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 369 | 370 | // Act 371 | instance.SetOnlyProperty = "some text"; 372 | 373 | // Assert 374 | Assert.That(cache.NumRemoveCalls, Is.EqualTo(0)); 375 | } 376 | 377 | [Test] 378 | public void IndividualCache_ShouldNotCacheAutoproperties() 379 | { 380 | // Arrange 381 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 382 | dynamic instance = WeaverHelper.CreateInstance(Assembly, cache); 383 | 384 | // Act 385 | dynamic value = instance.AutoProperty; 386 | value = instance.AutoProperty; 387 | 388 | // Assert 389 | Assert.IsTrue(cache.NumStoreCalls == 0); 390 | Assert.IsTrue(cache.NumRetrieveCalls == 0); 391 | } 392 | } 393 | } -------------------------------------------------------------------------------- /MethodCache.Fody/CecilHelper.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Fody 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Mono.Cecil; 7 | using Mono.Cecil.Cil; 8 | using Mono.Cecil.Rocks; 9 | 10 | public static class CecilHelper 11 | { 12 | public static int AddVariable(this MethodDefinition method, TypeReference typeReference) 13 | { 14 | method.Body.Variables.Add(method.Module.ImportVariable(typeReference)); 15 | 16 | return method.Body.Variables.Count - 1; 17 | } 18 | 19 | public static Instruction Append(this Instruction instruction, Instruction instructionAfter, ILProcessor processor) 20 | { 21 | processor.InsertAfter(instruction, instructionAfter); 22 | 23 | return instructionAfter; 24 | } 25 | 26 | public static Instruction AppendBoxIfNecessary(this Instruction instruction, ILProcessor processor, 27 | TypeReference typeReference) 28 | { 29 | if (typeReference.IsValueType || typeReference.IsGenericParameter) 30 | { 31 | return instruction.Append(processor.Create(OpCodes.Box, typeReference), processor); 32 | } 33 | 34 | return instruction; 35 | } 36 | 37 | public static Instruction AppendDup(this Instruction instruction, ILProcessor processor) 38 | { 39 | return instruction.Append(processor.Create(OpCodes.Dup), processor); 40 | } 41 | 42 | public static Instruction AppendLd(this Instruction instruction, ILProcessor processor, 43 | CustomAttributeArgument argument, References references) 44 | { 45 | // Tested with the following types (see ECMA-334, 24.1.3 attribute parameter types) 46 | // bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort. 47 | // object 48 | // System.Type 49 | // An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (§17.2) 50 | // Single-dimensional arrays of the above types 51 | TypeReference argumentType = argument.Type; 52 | 53 | switch (argumentType.MetadataType) 54 | { 55 | case MetadataType.ValueType: 56 | if (argumentType.Resolve().IsEnum == false) 57 | { 58 | throw new ArgumentException("Type Enum expected.", "argument"); 59 | } 60 | 61 | // Get underlying Enum type 62 | argumentType = argument.Type.Resolve().Fields.First(field => field.Name == "value__").FieldType; 63 | break; 64 | 65 | case MetadataType.Object: 66 | return instruction.AppendLd(processor, (CustomAttributeArgument)argument.Value, references); 67 | 68 | case MetadataType.Array: 69 | CustomAttributeArgument[] values = (CustomAttributeArgument[])argument.Value; 70 | 71 | instruction = instruction 72 | .AppendLdcI4(processor, values.Length) 73 | .Append(processor.Create(OpCodes.Newarr, argument.Type.GetElementType()), processor); 74 | 75 | TypeReference arrayType = argument.Type.GetElementType(); 76 | 77 | for (int i = 0; i < values.Length; i++) 78 | { 79 | instruction = instruction 80 | .AppendDup(processor) 81 | .AppendLdcI4(processor, i) 82 | .AppendLd(processor, values[i], references); 83 | 84 | if (arrayType == references.ModuleDefinition.TypeSystem.Object) 85 | { 86 | instruction = instruction.AppendBoxIfNecessary(processor, ((CustomAttributeArgument)values[i].Value).Type); 87 | } 88 | else if (argumentType.Resolve().IsEnum) 89 | { 90 | // Get underlying Enum type 91 | arrayType = argument.Type.Resolve().Fields.First(field => field.Name == "value__").FieldType; 92 | } 93 | 94 | if (arrayType.IsValueType) 95 | { 96 | switch (arrayType.MetadataType) 97 | { 98 | case MetadataType.Boolean: 99 | case MetadataType.SByte: 100 | case MetadataType.Byte: 101 | instruction = instruction.Append(processor.Create(OpCodes.Stelem_I1), processor); 102 | break; 103 | 104 | case MetadataType.Char: 105 | case MetadataType.Int16: 106 | case MetadataType.UInt16: 107 | instruction = instruction.Append(processor.Create(OpCodes.Stelem_I2), processor); 108 | break; 109 | 110 | case MetadataType.Int32: 111 | case MetadataType.UInt32: 112 | instruction = instruction.Append(processor.Create(OpCodes.Stelem_I4), processor); 113 | break; 114 | 115 | case MetadataType.Int64: 116 | case MetadataType.UInt64: 117 | instruction = instruction.Append(processor.Create(OpCodes.Stelem_I8), processor); 118 | break; 119 | 120 | case MetadataType.Single: 121 | instruction = instruction.Append(processor.Create(OpCodes.Stelem_R4), processor); 122 | break; 123 | 124 | case MetadataType.Double: 125 | instruction = instruction.Append(processor.Create(OpCodes.Stelem_R8), processor); 126 | break; 127 | 128 | default: 129 | throw new ArgumentException("Unrecognized array value type.", "argument"); 130 | } 131 | } 132 | else 133 | { 134 | instruction = instruction.Append(processor.Create(OpCodes.Stelem_Ref), processor); 135 | } 136 | } 137 | 138 | return instruction; 139 | } 140 | 141 | switch (argumentType.MetadataType) 142 | { 143 | case MetadataType.Boolean: 144 | case MetadataType.SByte: 145 | return instruction.Append(processor.Create(OpCodes.Ldc_I4_S, Convert.ToSByte(argument.Value)), processor); 146 | 147 | case MetadataType.Int16: 148 | return instruction.Append(processor.Create(OpCodes.Ldc_I4, Convert.ToInt16(argument.Value)), processor); 149 | 150 | case MetadataType.Int32: 151 | return instruction.Append(processor.Create(OpCodes.Ldc_I4, Convert.ToInt32(argument.Value)), processor); 152 | 153 | case MetadataType.Int64: 154 | return instruction.Append(processor.Create(OpCodes.Ldc_I8, Convert.ToInt64(argument.Value)), processor); 155 | 156 | case MetadataType.Byte: 157 | return instruction.Append(processor.Create(OpCodes.Ldc_I4, Convert.ToInt32(argument.Value)), processor); 158 | 159 | case MetadataType.UInt16: 160 | unchecked 161 | { 162 | return instruction.Append(processor.Create(OpCodes.Ldc_I4, (Int16)(UInt16)argument.Value), processor); 163 | } 164 | 165 | case MetadataType.Char: 166 | return instruction.Append(processor.Create(OpCodes.Ldc_I4, Convert.ToChar(argument.Value)), processor); 167 | 168 | case MetadataType.UInt32: 169 | unchecked 170 | { 171 | return instruction.Append(processor.Create(OpCodes.Ldc_I4, (Int32)(UInt32)argument.Value), processor); 172 | } 173 | 174 | case MetadataType.UInt64: 175 | unchecked 176 | { 177 | return instruction.Append(processor.Create(OpCodes.Ldc_I8, (Int64)(UInt64)argument.Value), processor); 178 | } 179 | 180 | case MetadataType.Single: 181 | return instruction.Append(processor.Create(OpCodes.Ldc_R4, Convert.ToSingle(argument.Value)), processor); 182 | 183 | case MetadataType.Double: 184 | return instruction.Append(processor.Create(OpCodes.Ldc_R8, Convert.ToDouble(argument.Value)), processor); 185 | 186 | case MetadataType.String: 187 | return instruction.Append(processor.Create(OpCodes.Ldstr, (string)argument.Value), processor); 188 | 189 | case MetadataType.Class: 190 | if (argumentType.Resolve().IsDefinition == false) 191 | { 192 | throw new ArgumentException("Type Type expected.", "argument"); 193 | } 194 | 195 | return 196 | instruction 197 | .Append(processor.Create(OpCodes.Ldtoken, (TypeReference)argument.Value), processor) 198 | .Append(processor.Create(OpCodes.Call, references.ModuleDefinition.Import( 199 | references.SystemTypeGetTypeFromHandleMethod)), processor); 200 | 201 | default: 202 | throw new ArgumentException("Unrecognized attribute parameter type.", "argument"); 203 | } 204 | } 205 | 206 | public static Instruction AppendLdarg(this Instruction instruction, ILProcessor processor, int index) 207 | { 208 | return instruction.Append(processor.Create(OpCodes.Ldarg, index), processor); 209 | } 210 | 211 | public static Instruction AppendLdcI4(this Instruction instruction, ILProcessor processor, int value) 212 | { 213 | return instruction.Append(processor.Create(OpCodes.Ldc_I4, value), processor); 214 | } 215 | 216 | public static Instruction AppendLdloc(this Instruction instruction, ILProcessor processor, int index) 217 | { 218 | return instruction.Append(processor.Create(OpCodes.Ldloc, index), processor); 219 | } 220 | 221 | public static Instruction AppendLdstr(this Instruction instruction, ILProcessor processor, string str) 222 | { 223 | return instruction.Append(processor.Create(OpCodes.Ldstr, str), processor); 224 | } 225 | 226 | public static Instruction AppendStloc(this Instruction instruction, ILProcessor processor, int index) 227 | { 228 | return instruction.Append(processor.Create(OpCodes.Stloc, index), processor); 229 | } 230 | 231 | public static bool ContainsAttribute(this ICustomAttributeProvider methodDefinition, TypeDefinition attributeType) 232 | { 233 | return methodDefinition.CustomAttributes.Any(x => x.Constructor.DeclaringType.FullName == attributeType.FullName); 234 | } 235 | 236 | public static bool ContainsAttribute(this ICustomAttributeProvider methodDefinition, string attributeTypeName) 237 | { 238 | return methodDefinition.CustomAttributes.Any(x => x.Constructor.DeclaringType.Name == attributeTypeName); 239 | } 240 | 241 | public static MethodDefinition GetInheritedPropertyGet(this TypeDefinition baseType, string propertyName) 242 | { 243 | MethodDefinition methodDefinition = baseType.GetPropertyGet(propertyName); 244 | 245 | if (methodDefinition == null && baseType.BaseType != null) 246 | { 247 | return baseType.BaseType.Resolve().GetInheritedPropertyGet(propertyName); 248 | } 249 | 250 | if (methodDefinition == null && baseType.BaseType == null) 251 | { 252 | return null; 253 | } 254 | 255 | if (methodDefinition.IsPrivate) 256 | { 257 | return null; 258 | } 259 | 260 | return methodDefinition; 261 | } 262 | 263 | public static MethodDefinition GetMethod(this TypeDefinition typeDefinition, string methodName, 264 | TypeReference returnType, TypeReference[] parameterTypes) 265 | { 266 | return typeDefinition.Methods.SingleOrDefault(x => x.Matches(methodName, returnType, parameterTypes)); 267 | } 268 | 269 | public static MethodDefinition GetPropertyGet(this TypeDefinition typeDefinition, string propertyName) 270 | { 271 | return typeDefinition.Properties.Where(x => x.Name == propertyName).Select(x => x.GetMethod).SingleOrDefault(); 272 | } 273 | 274 | public static MethodReference ImportMethod(this ModuleDefinition module, MethodDefinition methodDefinition) 275 | { 276 | return module.Import(methodDefinition); 277 | } 278 | 279 | public static VariableDefinition ImportVariable(this ModuleDefinition module, TypeReference typeReference) 280 | { 281 | return new VariableDefinition(module.Import(typeReference)); 282 | } 283 | 284 | public static bool IsEqualTo(this IEnumerable collection, IEnumerable collectionToCompare) 285 | { 286 | int index = 0; 287 | 288 | IEnumerable list = collection as IList ?? collection.ToList(); 289 | IEnumerable listToCompare = collectionToCompare as IList ?? collectionToCompare.ToList(); 290 | 291 | if (list.Count() != listToCompare.Count()) 292 | { 293 | return false; 294 | } 295 | 296 | foreach (T item in list) 297 | { 298 | if (!EqualityComparer.Default.Equals(item, listToCompare.ElementAt(index))) 299 | { 300 | return false; 301 | } 302 | 303 | index++; 304 | } 305 | 306 | return true; 307 | } 308 | 309 | public static MethodReference MakeGeneric(this MethodReference method, params TypeReference[] arguments) 310 | { 311 | if (method.GenericParameters.Count != arguments.Length) 312 | { 313 | throw new ArgumentException("Invalid number of generic type arguments supplied"); 314 | } 315 | 316 | if (arguments.Length == 0) 317 | { 318 | return method; 319 | } 320 | 321 | GenericInstanceMethod genericTypeReference = new GenericInstanceMethod(method); 322 | 323 | foreach (TypeReference argument in arguments) 324 | { 325 | genericTypeReference.GenericArguments.Add(argument); 326 | } 327 | 328 | return genericTypeReference; 329 | } 330 | 331 | public static MethodReference MakeHostInstanceGeneric(this MethodReference self, params TypeReference[] args) 332 | { 333 | MethodReference reference = new MethodReference(self.Name, self.ReturnType, self.DeclaringType.MakeGenericInstanceType(args)) 334 | { 335 | HasThis = self.HasThis, 336 | ExplicitThis = self.ExplicitThis, 337 | CallingConvention = self.CallingConvention 338 | }; 339 | 340 | foreach (ParameterDefinition parameter in self.Parameters) 341 | { 342 | reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); 343 | } 344 | 345 | foreach (GenericParameter genericParam in self.GenericParameters) 346 | { 347 | reference.GenericParameters.Add(new GenericParameter(genericParam.Name, reference)); 348 | } 349 | 350 | return reference; 351 | } 352 | 353 | public static bool Matches(this MethodDefinition methodDefinition, string methodName, TypeReference returnType, 354 | TypeReference[] parameters) 355 | { 356 | return methodDefinition.Name == methodName && methodDefinition.ReturnType.FullName == returnType.FullName && 357 | methodDefinition.Parameters.ToList() 358 | .Select(parameter => parameter.ParameterType.FullName) 359 | .IsEqualTo(parameters.Select(parameter => parameter.FullName)); 360 | } 361 | 362 | public static Instruction Prepend(this Instruction instruction, Instruction instructionBefore, ILProcessor processor) 363 | { 364 | processor.InsertBefore(instruction, instructionBefore); 365 | 366 | return instructionBefore; 367 | } 368 | 369 | public static void RemoveAttribute(this ICustomAttributeProvider methodDefinition, string attributeTypeName) 370 | { 371 | methodDefinition.CustomAttributes.Where(x => x.Constructor.DeclaringType.Name == attributeTypeName) 372 | .ToList() 373 | .ForEach(x => methodDefinition.CustomAttributes.Remove(x)); 374 | } 375 | } 376 | } -------------------------------------------------------------------------------- /MethodCache.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | DO_NOT_SHOW 3 | DO_NOT_SHOW 4 | DO_NOT_SHOW 5 | DO_NOT_SHOW 6 | DO_NOT_SHOW 7 | DO_NOT_SHOW 8 | DO_NOT_SHOW 9 | DO_NOT_SHOW 10 | DO_NOT_SHOW 11 | DO_NOT_SHOW 12 | DO_NOT_SHOW 13 | DO_NOT_SHOW 14 | DO_NOT_SHOW 15 | DO_NOT_SHOW 16 | DO_NOT_SHOW 17 | DO_NOT_SHOW 18 | DO_NOT_SHOW 19 | DO_NOT_SHOW 20 | DO_NOT_SHOW 21 | DO_NOT_SHOW 22 | DO_NOT_SHOW 23 | DO_NOT_SHOW 24 | DO_NOT_SHOW 25 | DO_NOT_SHOW 26 | DO_NOT_SHOW 27 | DO_NOT_SHOW 28 | DO_NOT_SHOW 29 | DO_NOT_SHOW 30 | DO_NOT_SHOW 31 | DO_NOT_SHOW 32 | 33 | 34 | DO_NOT_SHOW 35 | DO_NOT_SHOW 36 | <?xml version="1.0" encoding="utf-16"?><Profile name="Default"><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><HtmlReformatCode>True</HtmlReformatCode><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_BOTH</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><JsInsertSemicolon>True</JsInsertSemicolon><JsReformatCode>True</JsReformatCode><CssAlphabetizeProperties>True</CssAlphabetizeProperties><CssReformatCode>True</CssReformatCode><XMLReformatCode>True</XMLReformatCode><StyleCop.Documentation><SA1600ElementsMustBeDocumented>False</SA1600ElementsMustBeDocumented><SA1604ElementDocumentationMustHaveSummary>True</SA1604ElementDocumentationMustHaveSummary><SA1609PropertyDocumentationMustHaveValueDocumented>True</SA1609PropertyDocumentationMustHaveValueDocumented><SA1611ElementParametersMustBeDocumented>True</SA1611ElementParametersMustBeDocumented><SA1615ElementReturnValueMustBeDocumented>True</SA1615ElementReturnValueMustBeDocumented><SA1617VoidReturnValueMustNotBeDocumented>True</SA1617VoidReturnValueMustNotBeDocumented><SA1618GenericTypeParametersMustBeDocumented>True</SA1618GenericTypeParametersMustBeDocumented><SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes>True</SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes><SA1628DocumentationTextMustBeginWithACapitalLetter>True</SA1628DocumentationTextMustBeginWithACapitalLetter><SA1629DocumentationTextMustEndWithAPeriod>True</SA1629DocumentationTextMustEndWithAPeriod><SA1633SA1641UpdateFileHeader>Ignore</SA1633SA1641UpdateFileHeader><SA1639FileHeaderMustHaveSummary>True</SA1639FileHeaderMustHaveSummary><SA1642ConstructorSummaryDocumentationMustBeginWithStandardText>True</SA1642ConstructorSummaryDocumentationMustBeginWithStandardText><SA1643DestructorSummaryDocumentationMustBeginWithStandardText>True</SA1643DestructorSummaryDocumentationMustBeginWithStandardText><SA1644DocumentationHeadersMustNotContainBlankLines>True</SA1644DocumentationHeadersMustNotContainBlankLines></StyleCop.Documentation><StyleCop.Spacing><SA1001CommasMustBeSpacedCorrectly>False</SA1001CommasMustBeSpacedCorrectly><SA1005SingleLineCommentsMustBeginWithSingleSpace>True</SA1005SingleLineCommentsMustBeginWithSingleSpace><SA1006PreprocessorKeywordsMustNotBePrecededBySpace>True</SA1006PreprocessorKeywordsMustNotBePrecededBySpace><SA1021NegativeSignsMustBeSpacedCorrectly>True</SA1021NegativeSignsMustBeSpacedCorrectly><SA1022PositiveSignsMustBeSpacedCorrectly>True</SA1022PositiveSignsMustBeSpacedCorrectly><SA1025CodeMustNotContainMultipleWhitespaceInARow>True</SA1025CodeMustNotContainMultipleWhitespaceInARow></StyleCop.Spacing></Profile> 37 | Default 38 | USE_TABS_ONLY 39 | False 40 | SEPARATE 41 | False 42 | USE_FOR_FIELD 43 | False 44 | True 45 | False 46 | False 47 | True 48 | False 49 | OneStep 50 | OneStep 51 | 1 52 | OneStep 53 | OneStep 54 | <?xml version="1.0" encoding="utf-8" ?> 55 | <Patterns xmlns="urn:shemas-jetbrains-com:member-reordering-patterns"> 56 | 57 | <!--Do not reorder COM interfaces--> 58 | <Pattern> 59 | <Match> 60 | <And Weight="100"> 61 | <Kind Is="interface"/> 62 | <HasAttribute CLRName="System.Runtime.InteropServices.InterfaceTypeAttribute"/> 63 | </And> 64 | </Match> 65 | </Pattern> 66 | 67 | <!--Default pattern--> 68 | <Pattern RemoveAllRegions="true"> 69 | <Entry> 70 | <Sort> 71 | <Kind Order="constant field constructor destructor delegate event enum interface property indexer operator method struct class" /> 72 | <Access Order="public protected internal private" /> 73 | <Static /> 74 | <Readonly /> 75 | <Name /> 76 | </Sort> 77 | </Entry> 78 | 79 | <!--all other members--> 80 | <Entry /> 81 | </Pattern> 82 | </Patterns> 83 | CustomLayout 84 | True 85 | False 86 | True 87 | IL 88 | PE 89 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 90 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 91 | ALWAYS_ADD 92 | ALWAYS_ADD 93 | ALWAYS_ADD 94 | ALWAYS_ADD 95 | ALWAYS_ADD 96 | ALWAYS_ADD 97 | 1 98 | 1 99 | 0 100 | 1 101 | 1 102 | 1 103 | 1 104 | False 105 | LINE_BREAK 106 | True 107 | 108 | WRAP_IF_LONG 109 | WRAP_IF_LONG 110 | WRAP_IF_LONG 111 | False 112 | False 113 | True 114 | -------------------------------------------------------------------------------- /MethodCache.Tests/ParameterizedCacheAttributeWithParameterTests.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Tests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using MethodCache.Tests.TestAssembly; 6 | using MethodCache.Tests.TestAssembly.Cache; 7 | using MethodCache.Tests.TestAssembly.Cache.ComplexCacheAttribute; 8 | using NUnit.Framework; 9 | 10 | public class ParameterizedCacheAttributeWithParameterTests : ModuleWeaverTestsBase 11 | { 12 | [Test] 13 | public void TestClassWithParameterizedCacheAttributeClassLevelCacheAttributeAddsParameters() 14 | { 15 | // Arrange 16 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 17 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 18 | 19 | // Act 20 | testClass1.CacheParameterMethod("1", 2); 21 | testClass1.CacheParameterMethod("1", 2); 22 | 23 | // Assert 24 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 25 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 26 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 2); 27 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["Parameter1"] == "CacheParameter"); 28 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["Parameter2"] == 1); 29 | } 30 | 31 | [Test] 32 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParameterArrays() 33 | { 34 | // Arrange 35 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 36 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 37 | 38 | // Act 39 | testClass1.ComplexCacheParameterMethodArrays(); 40 | 41 | // Assert 42 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 43 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 12); 44 | 45 | bool[] boolArray = (bool[])cache.ParametersPassedToLastStoreCall["ParameterBoolArray"]; 46 | Assert.IsTrue(boolArray[0] == false); 47 | Assert.IsTrue(boolArray[1] == true); 48 | 49 | byte[] byteArray = (byte[])cache.ParametersPassedToLastStoreCall["ParameterByteArray"]; 50 | Assert.IsTrue(byteArray[0] == Byte.MinValue); 51 | Assert.IsTrue(byteArray[1] == Byte.MaxValue); 52 | 53 | char[] charArray = (char[])cache.ParametersPassedToLastStoreCall["ParameterCharacterArray"]; 54 | Assert.IsTrue(charArray[0] == Char.MinValue); 55 | Assert.IsTrue(charArray[1] == Char.MaxValue); 56 | 57 | double[] doubleArray = (double[])cache.ParametersPassedToLastStoreCall["ParameterDoubleArray"]; 58 | Assert.IsTrue(doubleArray[0] == Double.MinValue); 59 | Assert.IsTrue(doubleArray[1] == Double.MaxValue); 60 | 61 | float[] floatArray = (float[])cache.ParametersPassedToLastStoreCall["ParameterFloatArray"]; 62 | Assert.IsTrue(floatArray[0] == Single.MinValue); 63 | Assert.IsTrue(floatArray[1] == Single.MaxValue); 64 | 65 | int[] intArray = (int[])cache.ParametersPassedToLastStoreCall["ParameterIntArray"]; 66 | Assert.IsTrue(intArray[0] == Int32.MinValue); 67 | Assert.IsTrue(intArray[1] == Int32.MaxValue); 68 | 69 | long[] longArray = (long[])cache.ParametersPassedToLastStoreCall["ParameterLongArray"]; 70 | Assert.IsTrue(longArray[0] == Int64.MinValue); 71 | Assert.IsTrue(longArray[1] == Int64.MaxValue); 72 | 73 | sbyte[] sByteArray = (sbyte[])cache.ParametersPassedToLastStoreCall["ParameterSByteArray"]; 74 | Assert.IsTrue(sByteArray[0] == SByte.MinValue); 75 | Assert.IsTrue(sByteArray[1] == SByte.MaxValue); 76 | 77 | short[] shortArray = (short[])cache.ParametersPassedToLastStoreCall["ParameterShortArray"]; 78 | Assert.IsTrue(shortArray[0] == Int16.MinValue); 79 | Assert.IsTrue(shortArray[1] == Int16.MaxValue); 80 | 81 | uint[] uIntArray = (uint[])cache.ParametersPassedToLastStoreCall["ParameterUIntArray"]; 82 | Assert.IsTrue(uIntArray[0] == UInt32.MinValue); 83 | Assert.IsTrue(uIntArray[1] == UInt32.MaxValue); 84 | 85 | ulong[] uLongArray = (ulong[])cache.ParametersPassedToLastStoreCall["ParameterULongArray"]; 86 | Assert.IsTrue(uLongArray[0] == UInt64.MinValue); 87 | Assert.IsTrue(uLongArray[1] == UInt64.MaxValue); 88 | 89 | ushort[] uShortArray = (ushort[])cache.ParametersPassedToLastStoreCall["ParameterUShortArray"]; 90 | Assert.IsTrue(uShortArray[0] == UInt16.MinValue); 91 | Assert.IsTrue(uShortArray[1] == UInt16.MaxValue); 92 | } 93 | 94 | [Test] 95 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersEnum() 96 | { 97 | // Arrange 98 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 99 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 100 | 101 | // Act 102 | testClass1.ComplexCacheParameterMethodEnum(); 103 | 104 | // Assert 105 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 106 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 1); 107 | 108 | dynamic type = WeaverHelper.CreateType(Assembly); 109 | 110 | Assert.IsInstanceOfType(type, cache.ParametersPassedToLastStoreCall["ParameterEnum"]); 111 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterEnum"] == 112 | (Enum.GetValues(type)[0] | Enum.GetValues(type)[2])); 113 | } 114 | 115 | [Test] 116 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersEnumArray() 117 | { 118 | // Arrange 119 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 120 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 121 | 122 | // Act 123 | testClass1.ComplexCacheParameterMethodEnumArray(); 124 | 125 | // Assert 126 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 127 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 1); 128 | 129 | dynamic type = WeaverHelper.CreateType(Assembly); 130 | dynamic typeArray = WeaverHelper.CreateType(Assembly); 131 | 132 | Assert.IsInstanceOfType(typeArray, cache.ParametersPassedToLastStoreCall["ParameterEnumArray"]); 133 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterEnumArray"][0] == 134 | (Enum.GetValues(type)[0] | Enum.GetValues(type)[2])); 135 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterEnumArray"][1] == Enum.GetValues(type)[1]); 136 | } 137 | 138 | [Test] 139 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersMax() 140 | { 141 | // Arrange 142 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 143 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 144 | 145 | // Act 146 | testClass1.ComplexCacheParameterMethodMax(); 147 | 148 | // Assert 149 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 150 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 12); 151 | Assert.IsTrue((bool)cache.ParametersPassedToLastStoreCall["ParameterBool"] == false); 152 | Assert.IsTrue((byte)cache.ParametersPassedToLastStoreCall["ParameterByte"] == Byte.MaxValue); 153 | Assert.IsTrue((char)cache.ParametersPassedToLastStoreCall["ParameterCharacter"] == Char.MaxValue); 154 | Assert.IsTrue((double)cache.ParametersPassedToLastStoreCall["ParameterDouble"] == Double.MaxValue); 155 | Assert.IsTrue((float)cache.ParametersPassedToLastStoreCall["ParameterFloat"] == Single.MaxValue); 156 | Assert.IsTrue((int)cache.ParametersPassedToLastStoreCall["ParameterInt"] == Int32.MaxValue); 157 | Assert.IsTrue((long)cache.ParametersPassedToLastStoreCall["ParameterLong"] == Int64.MaxValue); 158 | Assert.IsTrue((sbyte)cache.ParametersPassedToLastStoreCall["ParameterSByte"] == SByte.MaxValue); 159 | Assert.IsTrue((short)cache.ParametersPassedToLastStoreCall["ParameterShort"] == Int16.MaxValue); 160 | Assert.IsTrue((uint)cache.ParametersPassedToLastStoreCall["ParameterUInt"] == UInt32.MaxValue); 161 | Assert.IsTrue((ulong)cache.ParametersPassedToLastStoreCall["ParameterULong"] == UInt64.MaxValue); 162 | Assert.IsTrue((ushort)cache.ParametersPassedToLastStoreCall["ParameterUShort"] == UInt16.MaxValue); 163 | } 164 | 165 | [Test] 166 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersMin() 167 | { 168 | // Arrange 169 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 170 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 171 | 172 | // Act 173 | testClass1.ComplexCacheParameterMethodMin(); 174 | 175 | // Assert 176 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 177 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 12); 178 | Assert.IsTrue((bool)cache.ParametersPassedToLastStoreCall["ParameterBool"] == false); 179 | Assert.IsTrue((byte)cache.ParametersPassedToLastStoreCall["ParameterByte"] == Byte.MinValue); 180 | Assert.IsTrue((char)cache.ParametersPassedToLastStoreCall["ParameterCharacter"] == Char.MinValue); 181 | Assert.IsTrue((double)cache.ParametersPassedToLastStoreCall["ParameterDouble"] == Double.MinValue); 182 | Assert.IsTrue((float)cache.ParametersPassedToLastStoreCall["ParameterFloat"] == Single.MinValue); 183 | Assert.IsTrue((int)cache.ParametersPassedToLastStoreCall["ParameterInt"] == Int32.MinValue); 184 | Assert.IsTrue((long)cache.ParametersPassedToLastStoreCall["ParameterLong"] == Int64.MinValue); 185 | Assert.IsTrue((sbyte)cache.ParametersPassedToLastStoreCall["ParameterSByte"] == SByte.MinValue); 186 | Assert.IsTrue((short)cache.ParametersPassedToLastStoreCall["ParameterShort"] == Int16.MinValue); 187 | Assert.IsTrue((uint)cache.ParametersPassedToLastStoreCall["ParameterUInt"] == UInt32.MinValue); 188 | Assert.IsTrue((ulong)cache.ParametersPassedToLastStoreCall["ParameterULong"] == UInt64.MinValue); 189 | Assert.IsTrue((ushort)cache.ParametersPassedToLastStoreCall["ParameterUShort"] == UInt16.MinValue); 190 | } 191 | 192 | [Test] 193 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersObjectArray() 194 | { 195 | // Arrange 196 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 197 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 198 | 199 | // Act 200 | testClass1.ComplexCacheParameterMethodObjectArray(); 201 | 202 | // Assert 203 | dynamic type = WeaverHelper.CreateType(Assembly); 204 | 205 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 206 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 1); 207 | Assert.IsInstanceOfType(typeof(object[]), cache.ParametersPassedToLastStoreCall["ParameterObjectArray"]); 208 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObjectArray"].Length == 5); 209 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObjectArray"][0] == 1); 210 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObjectArray"][1] == "2"); 211 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObjectArray"][2] == Enum.GetValues(type)[2]); 212 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObjectArray"][3] == typeof(List)); 213 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObjectArray"][4][0] == 1); 214 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObjectArray"][4][1] == "2"); 215 | } 216 | 217 | [Test] 218 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersObjectArrayString() 219 | { 220 | // Arrange 221 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 222 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 223 | 224 | // Act 225 | testClass1.ComplexCacheParameterMethodObjectArrayString(); 226 | 227 | // Assert 228 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 229 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 1); 230 | Assert.IsInstanceOfType(typeof(string[]), cache.ParametersPassedToLastStoreCall["ParameterObject"]); 231 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObject"].Length == 2); 232 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObject"][0] == "1"); 233 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObject"][1] == "2"); 234 | } 235 | 236 | [Test] 237 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersObjectEnum() 238 | { 239 | // Arrange 240 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 241 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 242 | 243 | // Act 244 | testClass1.ComplexCacheParameterMethodObjectEnum(); 245 | 246 | // Assert 247 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 248 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 1); 249 | 250 | dynamic type = WeaverHelper.CreateType(Assembly); 251 | 252 | Assert.IsInstanceOfType(type, cache.ParametersPassedToLastStoreCall["ParameterObject"]); 253 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObject"] == 254 | (Enum.GetValues(type)[0] | Enum.GetValues(type)[2])); 255 | } 256 | 257 | [Test] 258 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersObjectType() 259 | { 260 | // Arrange 261 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 262 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 263 | 264 | // Act 265 | testClass1.ComplexCacheParameterMethodObjectType(); 266 | 267 | // Assert 268 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 269 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 1); 270 | Assert.IsInstanceOfType(typeof(Type), cache.ParametersPassedToLastStoreCall["ParameterObject"]); 271 | Assert.IsTrue((Type)cache.ParametersPassedToLastStoreCall["ParameterObject"] == typeof(List)); 272 | } 273 | 274 | [Test] 275 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersObjectValueType() 276 | { 277 | // Arrange 278 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 279 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 280 | 281 | // Act 282 | testClass1.ComplexCacheParameterMethodObjectValueType(); 283 | 284 | // Assert 285 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 286 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 1); 287 | Assert.IsInstanceOfType(typeof(bool), cache.ParametersPassedToLastStoreCall["ParameterObject"]); 288 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterObject"] == true); 289 | } 290 | 291 | [Test] 292 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersStringArray() 293 | { 294 | // Arrange 295 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 296 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 297 | 298 | // Act 299 | testClass1.ComplexCacheParameterMethodStringArray(); 300 | 301 | // Assert 302 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 303 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 1); 304 | Assert.IsInstanceOfType(typeof(string[]), cache.ParametersPassedToLastStoreCall["ParameterStringArray"]); 305 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterStringArray"].Length == 2); 306 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterStringArray"][0] == "1"); 307 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["ParameterStringArray"][1] == "2"); 308 | } 309 | 310 | [Test] 311 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersType() 312 | { 313 | // Arrange 314 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 315 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 316 | 317 | // Act 318 | testClass1.ComplexCacheParameterMethodType(); 319 | 320 | // Assert 321 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 322 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 1); 323 | Assert.IsInstanceOfType(typeof(Type), cache.ParametersPassedToLastStoreCall["ParameterType"]); 324 | Assert.IsTrue((Type)cache.ParametersPassedToLastStoreCall["ParameterType"] == typeof(List)); 325 | } 326 | 327 | [Test] 328 | public void TestClassWithParameterizedCacheAttributeComplexCacheAttributePassesParametersTypeArray() 329 | { 330 | // Arrange 331 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 332 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 333 | 334 | // Act 335 | testClass1.ComplexCacheParameterMethodTypeArray(); 336 | 337 | // Assert 338 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 339 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 1); 340 | Assert.IsInstanceOfType(typeof(Type[]), cache.ParametersPassedToLastStoreCall["ParameterTypeArray"]); 341 | Assert.IsTrue((Type)cache.ParametersPassedToLastStoreCall["ParameterTypeArray"][0] == typeof(List)); 342 | Assert.IsTrue((Type)cache.ParametersPassedToLastStoreCall["ParameterTypeArray"][1] == typeof(List)); 343 | } 344 | 345 | [Test] 346 | public void TestClassWithParameterizedCacheAttributeMethodLevelCacheAttributeAlsoWorksForFields() 347 | { 348 | // Arrange 349 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 350 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 351 | 352 | // Act 353 | testClass1.CacheParameterMethodWithField("1", 2); 354 | testClass1.CacheParameterMethodWithField("1", 2); 355 | 356 | // Assert 357 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 358 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 359 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 2); 360 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["Parameter3"] == true); 361 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["parameter3"] == 0); 362 | } 363 | 364 | [Test] 365 | public void TestClassWithParameterizedCacheAttributeMethodLevelCacheAttributeOverridesParameters() 366 | { 367 | // Arrange 368 | dynamic cache = WeaverHelper.CreateInstance(Assembly); 369 | dynamic testClass1 = WeaverHelper.CreateInstance(Assembly, cache); 370 | 371 | // Act 372 | testClass1.OverridenCacheParameterMethod("1", 2); 373 | testClass1.OverridenCacheParameterMethod("1", 2); 374 | 375 | // Assert 376 | Assert.IsTrue(cache.NumStoreParameterCalls == 1); 377 | Assert.IsTrue(cache.NumRetrieveCalls == 1); 378 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall.Count == 2); 379 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["Parameter2"] == 2); 380 | Assert.IsTrue(cache.ParametersPassedToLastStoreCall["Parameter3"] == true); 381 | } 382 | } 383 | } -------------------------------------------------------------------------------- /MethodCache.Fody/ModuleWeaver.cs: -------------------------------------------------------------------------------- 1 | namespace MethodCache.Fody 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Xml.Linq; 9 | using MethodCache.Attributes; 10 | using Mono.Cecil; 11 | using Mono.Cecil.Cil; 12 | using Mono.Cecil.Rocks; 13 | 14 | public class ModuleWeaver 15 | { 16 | public const string CacheAttributeName = "CacheAttribute"; 17 | 18 | public const string CacheGetterName = "Cache"; 19 | 20 | public const string CacheTypeContainsMethodName = "Contains"; 21 | 22 | public const string CacheTypeRemoveMethodName = "Remove"; 23 | 24 | public const string CacheTypeRetrieveMethodName = "Retrieve"; 25 | 26 | public const string CacheTypeStoreMethodName = "Store"; 27 | 28 | public const string NoCacheAttributeName = "NoCacheAttribute"; 29 | 30 | public ModuleWeaver() 31 | { 32 | LogInfo = m => { }; 33 | LogWarning = m => { }; 34 | LogWarningPoint = (m, p) => { }; 35 | LogError = m => { }; 36 | LogErrorPoint = (m, p) => { }; 37 | 38 | Config = new XElement("MethodCache"); 39 | DefineConstants = new List(); 40 | } 41 | 42 | public IAssemblyResolver AssemblyResolver { get; set; } 43 | 44 | public XElement Config { get; set; } 45 | 46 | public List DefineConstants { get; set; } 47 | 48 | public Action LogError { get; set; } 49 | 50 | public Action LogErrorPoint { get; set; } 51 | 52 | public Action LogInfo { get; set; } 53 | 54 | public Action LogWarning { get; set; } 55 | 56 | public Action LogWarningPoint { get; set; } 57 | 58 | public ModuleDefinition ModuleDefinition { get; set; } 59 | 60 | public References References { get; set; } 61 | 62 | private bool LogDebugOutput { get; set; } 63 | 64 | private bool MethodCacheEnabledByDefault { get; set; } 65 | 66 | private bool PropertyCacheEnabledByDefault { get; set; } 67 | 68 | public void Execute() 69 | { 70 | LogDebugOutput = DefineConstants.Any(x => x.ToLower() == "debug"); 71 | 72 | References = new References() { AssemblyResolver = AssemblyResolver, ModuleDefinition = ModuleDefinition }; 73 | References.LoadReferences(); 74 | 75 | MethodCacheEnabledByDefault = true; 76 | PropertyCacheEnabledByDefault = true; 77 | 78 | ReadConfiguration(); 79 | 80 | MethodsForWeaving methodToWeave = SelectMethods(); 81 | 82 | WeaveMethods(methodToWeave.Methods); 83 | WeaveProperties(methodToWeave.Properties); 84 | 85 | RemoveReference(); 86 | } 87 | 88 | private static string CreateCacheKeyString(MethodDefinition methodDefinition) 89 | { 90 | StringBuilder builder = new StringBuilder(); 91 | 92 | builder.Append(methodDefinition.DeclaringType.FullName); 93 | builder.Append("."); 94 | 95 | if (methodDefinition.IsSpecialName && (methodDefinition.IsSetter || methodDefinition.IsGetter)) 96 | { 97 | builder.Append(Regex.Replace(methodDefinition.Name, "[gs]et_", string.Empty)); 98 | } 99 | else 100 | { 101 | builder.Append(methodDefinition.Name); 102 | 103 | for (int i = 0; i < methodDefinition.Parameters.Count + methodDefinition.GenericParameters.Count; i++) 104 | { 105 | builder.Append(string.Format("_{{{0}}}", i)); 106 | } 107 | } 108 | 109 | return builder.ToString(); 110 | } 111 | 112 | private static MethodDefinition GetCacheGetter(MethodDefinition methodDefinition) 113 | { 114 | MethodDefinition propertyGet = methodDefinition.DeclaringType.GetPropertyGet(CacheGetterName); 115 | 116 | propertyGet = propertyGet ?? 117 | methodDefinition.DeclaringType.BaseType.Resolve().GetInheritedPropertyGet(CacheGetterName); 118 | 119 | return propertyGet; 120 | } 121 | 122 | private Instruction AppendDebugWrite(Instruction instruction, ILProcessor processor, string message) 123 | { 124 | return instruction 125 | .AppendLdstr(processor, message) 126 | .Append(processor.Create(OpCodes.Call, ModuleDefinition.ImportMethod(References.DebugWriteLineMethod)), processor); 127 | } 128 | 129 | private bool CacheAttributeConstructedWithParam(CustomAttribute attribute, Members cachedMembers) 130 | { 131 | if (attribute == null || attribute.ConstructorArguments.Count == 0) 132 | { 133 | return false; 134 | } 135 | 136 | return Equals(attribute.ConstructorArguments.Single().Value, (int)cachedMembers); 137 | } 138 | 139 | private bool CacheAttributeExcludesMethods(CustomAttribute attribute) 140 | { 141 | return CacheAttributeConstructedWithParam(attribute, Members.Properties); 142 | } 143 | 144 | private bool CacheAttributeExcludesProperties(CustomAttribute attribute) 145 | { 146 | return CacheAttributeConstructedWithParam(attribute, Members.Methods); 147 | } 148 | 149 | private bool CacheAttributeMembersExplicitly(CustomAttribute attribute, Members requiredFlag) 150 | { 151 | if (attribute == null || attribute.ConstructorArguments.Count == 0) 152 | { 153 | return false; 154 | } 155 | 156 | Members constructorArgument = (Members)attribute.ConstructorArguments.Single().Value; 157 | 158 | return constructorArgument.HasFlag(requiredFlag); 159 | } 160 | 161 | private MethodDefinition CacheTypeGetContainsMethod(TypeDefinition cacheType, string cacheTypeContainsMethodName) 162 | { 163 | return cacheType.GetMethod(cacheTypeContainsMethodName, ModuleDefinition.TypeSystem.Boolean, 164 | new[] { ModuleDefinition.TypeSystem.String }); 165 | } 166 | 167 | private MethodDefinition CacheTypeGetRemoveMethod(TypeDefinition cacheType, string cacheTypeRemoveMethodName) 168 | { 169 | return cacheType.GetMethod(cacheTypeRemoveMethodName, ModuleDefinition.TypeSystem.Void, 170 | new[] { ModuleDefinition.TypeSystem.String }); 171 | } 172 | 173 | private MethodDefinition CacheTypeGetRetrieveMethod(TypeDefinition cacheType, string cacheTypeRetrieveMethodName) 174 | { 175 | return cacheType.GetMethod(cacheTypeRetrieveMethodName, new GenericParameter("T", cacheType), 176 | new[] { ModuleDefinition.TypeSystem.String }); 177 | } 178 | 179 | private MethodDefinition CacheTypeGetStoreMethod(TypeDefinition cacheInterface, string cacheTypeStoreMethodName) 180 | { 181 | // Prioritize Store methods with parameters Dictionary 182 | MethodDefinition methodDefinition = cacheInterface.GetMethod(cacheTypeStoreMethodName, ModuleDefinition.TypeSystem.Void, 183 | new[] 184 | { 185 | ModuleDefinition.TypeSystem.String, ModuleDefinition.TypeSystem.Object, 186 | References.DictionaryInterface.MakeGenericInstanceType(ModuleDefinition.TypeSystem.String, 187 | ModuleDefinition.TypeSystem.Object) 188 | }); 189 | 190 | if (methodDefinition != null) 191 | { 192 | return methodDefinition; 193 | } 194 | 195 | return cacheInterface.GetMethod(cacheTypeStoreMethodName, ModuleDefinition.TypeSystem.Void, 196 | new[] { ModuleDefinition.TypeSystem.String, ModuleDefinition.TypeSystem.Object }); 197 | } 198 | 199 | private bool CheckCacheTypeMethods(TypeDefinition cacheType) 200 | { 201 | LogInfo(string.Format("Checking CacheType methods ({0}, {1}, {2}).", CacheTypeContainsMethodName, 202 | CacheTypeStoreMethodName, CacheTypeRetrieveMethodName)); 203 | 204 | if (CacheTypeGetContainsMethod(cacheType, CacheTypeContainsMethodName) == null) 205 | { 206 | LogWarning(string.Format("Method {0} missing in {1}.", CacheTypeContainsMethodName, cacheType.FullName)); 207 | 208 | return false; 209 | } 210 | 211 | if (CacheTypeGetStoreMethod(cacheType, CacheTypeStoreMethodName) == null) 212 | { 213 | LogWarning(string.Format("Method {0} missing in {1}.", CacheTypeStoreMethodName, cacheType.FullName)); 214 | 215 | return false; 216 | } 217 | 218 | if (CacheTypeGetRetrieveMethod(cacheType, CacheTypeRetrieveMethodName) == null) 219 | { 220 | LogWarning(string.Format("Method {0} missing in {1}.", CacheTypeRetrieveMethodName, cacheType.FullName)); 221 | 222 | return false; 223 | } 224 | 225 | LogInfo(string.Format("CacheInterface methods found.")); 226 | 227 | return true; 228 | } 229 | 230 | private bool ConfigHasAttribute(string name, string value) 231 | { 232 | return 233 | Config.Attributes().Any(x => x.Name.ToString().ToLower() == name.ToLower() && x.Value.ToLower() == value.ToLower()); 234 | } 235 | 236 | private IDictionary GetCacheAttributeConstructorFields(CustomAttribute attribute) 237 | { 238 | return attribute.Fields.ToDictionary(field => field.Name, field => field.Argument); 239 | } 240 | 241 | private IDictionary GetCacheAttributeConstructorParameters(CustomAttribute attribute) 242 | { 243 | return attribute.Properties.ToDictionary(property => property.Name, property => property.Argument); 244 | } 245 | 246 | private Instruction InjectCacheKeyCreatedCode(MethodDefinition methodDefinition, Instruction current, 247 | ILProcessor processor, int cacheKeyIndex) 248 | { 249 | if (LogDebugOutput) 250 | { 251 | // Call Debug.WriteLine with CacheKey 252 | current = current 253 | .AppendLdstr(processor, "CacheKey created: {0}") 254 | .AppendLdcI4(processor, 1) 255 | .Append(processor.Create(OpCodes.Newarr, ModuleDefinition.TypeSystem.Object), processor) 256 | .AppendDup(processor) 257 | .AppendLdcI4(processor, 0) 258 | .AppendLdloc(processor, cacheKeyIndex) 259 | .Append(processor.Create(OpCodes.Stelem_Ref), processor) 260 | .Append(processor.Create(OpCodes.Call, methodDefinition.Module.ImportMethod(References.StringFormatMethod)), 261 | processor) 262 | .Append(processor.Create(OpCodes.Call, methodDefinition.Module.ImportMethod(References.DebugWriteLineMethod)), 263 | processor); 264 | } 265 | 266 | return current; 267 | } 268 | 269 | private bool IsMethodValidForWeaving(MethodDefinition propertyGet, MethodDefinition methodDefinition) 270 | { 271 | if (propertyGet == null) 272 | { 273 | LogWarning(string.Format("Class {0} does not contain or inherit Getter {1}. Skip weaving of method {2}.", 274 | methodDefinition.DeclaringType.Name, CacheGetterName, methodDefinition.Name)); 275 | 276 | return false; 277 | } 278 | 279 | if (methodDefinition.IsStatic && !propertyGet.IsStatic) 280 | { 281 | LogWarning(string.Format("Method {2} of Class {0} is static, Getter {1} is not. Skip weaving of method {2}.", 282 | methodDefinition.DeclaringType.Name, CacheGetterName, methodDefinition.Name)); 283 | 284 | return false; 285 | } 286 | 287 | if (!CheckCacheTypeMethods(propertyGet.ReturnType.Resolve())) 288 | { 289 | LogWarning( 290 | string.Format( 291 | "ReturnType {0} of Getter {1} of Class {2} does not implement all methods. Skip weaving of method {3}.", 292 | propertyGet.ReturnType.Name, CacheGetterName, methodDefinition.DeclaringType.Name, methodDefinition.Name)); 293 | 294 | return false; 295 | } 296 | 297 | return true; 298 | } 299 | 300 | private bool IsPropertySetterValidForWeaving(MethodDefinition propertyGet, MethodDefinition methodDefinition) 301 | { 302 | if (!IsMethodValidForWeaving(propertyGet, methodDefinition)) 303 | { 304 | return false; 305 | } 306 | 307 | if (CacheTypeGetRemoveMethod(propertyGet.ReturnType.Resolve(), CacheTypeRemoveMethodName) == null) 308 | { 309 | LogWarning(string.Format("Method {0} missing in {1}.", CacheTypeRemoveMethodName, 310 | propertyGet.ReturnType.Resolve().FullName)); 311 | 312 | LogWarning( 313 | string.Format( 314 | "ReturnType {0} of Getter {1} of Class {2} does not implement all methods. Skip weaving of method {3}.", 315 | propertyGet.ReturnType.Name, CacheGetterName, methodDefinition.DeclaringType.Name, methodDefinition.Name)); 316 | 317 | return false; 318 | } 319 | 320 | return true; 321 | } 322 | 323 | private void ReadConfiguration() 324 | { 325 | if (ConfigHasAttribute("SkipDebugOutput", "true")) 326 | { 327 | LogWarning("Skipping Debug Output."); 328 | LogDebugOutput = false; 329 | } 330 | 331 | if (ConfigHasAttribute("CacheProperties", "false")) 332 | { 333 | LogWarning("Disabling property weaving."); 334 | PropertyCacheEnabledByDefault = false; 335 | } 336 | 337 | if (ConfigHasAttribute("CacheMethods", "false")) 338 | { 339 | LogWarning("Disabling method weaving."); 340 | MethodCacheEnabledByDefault = false; 341 | } 342 | } 343 | 344 | private void RemoveReference() 345 | { 346 | AssemblyNameReference referenceToRemove = 347 | ModuleDefinition.AssemblyReferences.FirstOrDefault(x => x.Name == "MethodCache.Attributes"); 348 | 349 | if (referenceToRemove == null) 350 | { 351 | LogInfo("No reference to 'MethodCache.Attributes.dll' found. References not modified."); 352 | 353 | return; 354 | } 355 | 356 | ModuleDefinition.AssemblyReferences.Remove(referenceToRemove); 357 | 358 | LogInfo("Removing reference to 'MethodCache.Attributes.dll'."); 359 | } 360 | 361 | private MethodsForWeaving SelectMethods() 362 | { 363 | LogInfo(string.Format("Searching for Methods and Properties in assembly ({0}).", ModuleDefinition.Name)); 364 | 365 | MethodsForWeaving result = new MethodsForWeaving(); 366 | 367 | foreach (TypeDefinition type in ModuleDefinition.Types) 368 | { 369 | foreach (MethodDefinition method in type.Methods) 370 | { 371 | if (ShouldWeaveMethod(method)) 372 | { 373 | // Store Cache attribute, method attribute takes precedence over class attributes 374 | CustomAttribute attribute = 375 | method.CustomAttributes.SingleOrDefault(x => x.Constructor.DeclaringType.Name == CacheAttributeName) ?? 376 | method.DeclaringType.CustomAttributes.SingleOrDefault( 377 | x => x.Constructor.DeclaringType.Name == CacheAttributeName); 378 | 379 | result.Add(method, attribute); 380 | } 381 | 382 | method.RemoveAttribute(CacheAttributeName); 383 | method.RemoveAttribute(NoCacheAttributeName); 384 | } 385 | 386 | foreach (PropertyDefinition property in type.Properties) 387 | { 388 | if (ShouldWeaveProperty(property)) 389 | { 390 | // Store Cache attribute, property attribute takes precedence over class attributes 391 | CustomAttribute attribute = 392 | property.CustomAttributes.SingleOrDefault(x => x.Constructor.DeclaringType.Name == CacheAttributeName) ?? 393 | property.DeclaringType.CustomAttributes.SingleOrDefault( 394 | x => x.Constructor.DeclaringType.Name == CacheAttributeName); 395 | 396 | result.Add(property, attribute); 397 | } 398 | 399 | property.RemoveAttribute(CacheAttributeName); 400 | property.RemoveAttribute(NoCacheAttributeName); 401 | } 402 | 403 | type.RemoveAttribute(CacheAttributeName); 404 | } 405 | 406 | return result; 407 | } 408 | 409 | private Instruction SetCacheKeyLocalVariable(Instruction current, MethodDefinition methodDefinition, 410 | ILProcessor processor, int cacheKeyIndex, int objectArrayIndex) 411 | { 412 | if (methodDefinition.IsSetter || methodDefinition.IsGetter) 413 | { 414 | return current.AppendStloc(processor, cacheKeyIndex); 415 | } 416 | else 417 | { 418 | // Create object[] for string.format 419 | int parameterCount = methodDefinition.Parameters.Count + methodDefinition.GenericParameters.Count; 420 | 421 | current = current 422 | .AppendLdcI4(processor, parameterCount) 423 | .Append(processor.Create(OpCodes.Newarr, ModuleDefinition.TypeSystem.Object), processor) 424 | .AppendStloc(processor, objectArrayIndex); 425 | 426 | 427 | // Set object[] values 428 | for (int i = 0; i < methodDefinition.GenericParameters.Count; i++) 429 | { 430 | current = current 431 | .AppendLdloc(processor, objectArrayIndex) 432 | .AppendLdcI4(processor, i) 433 | .Append(processor.Create(OpCodes.Ldtoken, methodDefinition.GenericParameters[i]), processor) 434 | .Append(processor.Create(OpCodes.Call, methodDefinition.Module.ImportMethod(References.SystemTypeGetTypeFromHandleMethod)), 435 | processor) 436 | .Append(processor.Create(OpCodes.Stelem_Ref), processor); 437 | } 438 | 439 | for (int i = 0; i < methodDefinition.Parameters.Count; i++) 440 | { 441 | current = current 442 | .AppendLdloc(processor, objectArrayIndex) 443 | .AppendLdcI4(processor, methodDefinition.GenericParameters.Count + i) 444 | .AppendLdarg(processor, !methodDefinition.IsStatic ? i + 1 : i) 445 | .AppendBoxIfNecessary(processor, methodDefinition.Parameters[i].ParameterType) 446 | .Append(processor.Create(OpCodes.Stelem_Ref), processor); 447 | } 448 | 449 | // Call string.format 450 | return current 451 | .AppendLdloc(processor, objectArrayIndex) 452 | .Append(processor.Create(OpCodes.Call, methodDefinition.Module.ImportMethod(References.StringFormatMethod)), 453 | processor) 454 | .AppendStloc(processor, cacheKeyIndex); 455 | } 456 | } 457 | 458 | private bool ShouldWeaveMethod(MethodDefinition method) 459 | { 460 | CustomAttribute classLevelCacheAttribute = 461 | method.DeclaringType.CustomAttributes.SingleOrDefault(x => x.Constructor.DeclaringType.Name == CacheAttributeName); 462 | 463 | bool hasClassLevelCache = classLevelCacheAttribute != null && 464 | !CacheAttributeExcludesMethods(classLevelCacheAttribute); 465 | bool hasMethodLevelCache = method.ContainsAttribute(CacheAttributeName); 466 | bool hasNoCacheAttribute = method.ContainsAttribute(NoCacheAttributeName); 467 | bool isSpecialName = method.IsSpecialName || method.IsGetter || method.IsSetter || method.IsConstructor; 468 | bool isCompilerGenerated = method.ContainsAttribute(References.CompilerGeneratedAttribute); 469 | 470 | if (hasNoCacheAttribute || isSpecialName || isCompilerGenerated) 471 | { 472 | // Never weave property accessors, special methods and compiler generated methods 473 | return false; 474 | } 475 | 476 | if (hasMethodLevelCache) 477 | { 478 | // Always weave methods explicitly marked for cache 479 | return true; 480 | } 481 | 482 | if (hasClassLevelCache && !CacheAttributeExcludesMethods(classLevelCacheAttribute)) 483 | { 484 | // Otherwise weave if marked at class level 485 | return MethodCacheEnabledByDefault || CacheAttributeMembersExplicitly(classLevelCacheAttribute, Members.Methods); 486 | } 487 | 488 | return false; 489 | } 490 | 491 | private bool ShouldWeaveProperty(PropertyDefinition property) 492 | { 493 | CustomAttribute classLevelCacheAttribute = 494 | property.DeclaringType.CustomAttributes.SingleOrDefault(x => x.Constructor.DeclaringType.Name == CacheAttributeName); 495 | 496 | bool hasClassLevelCache = classLevelCacheAttribute != null; 497 | bool hasPropertyLevelCache = property.ContainsAttribute(CacheAttributeName); 498 | bool hasNoCacheAttribute = property.ContainsAttribute(NoCacheAttributeName); 499 | bool isCacheGetter = property.Name == CacheGetterName; 500 | bool hasGetAccessor = property.GetMethod != null; 501 | bool isAutoProperty = hasGetAccessor && property.GetMethod.ContainsAttribute(References.CompilerGeneratedAttribute); 502 | 503 | if (hasNoCacheAttribute || isCacheGetter || isAutoProperty || !hasGetAccessor) 504 | { 505 | // Never weave Cache property, auto-properties, write-only properties and properties explicitly excluded 506 | return false; 507 | } 508 | 509 | if (hasPropertyLevelCache) 510 | { 511 | // Always weave properties explicitly marked for cache 512 | return true; 513 | } 514 | 515 | if (hasClassLevelCache && !CacheAttributeExcludesProperties(classLevelCacheAttribute)) 516 | { 517 | // Otherwise weave if marked at class level 518 | return PropertyCacheEnabledByDefault || 519 | CacheAttributeMembersExplicitly(classLevelCacheAttribute, Members.Properties); 520 | } 521 | 522 | return false; 523 | } 524 | 525 | private void WeaveMethod(MethodDefinition methodDefinition, CustomAttribute attribute, MethodReference propertyGet) 526 | { 527 | methodDefinition.Body.InitLocals = true; 528 | 529 | methodDefinition.Body.SimplifyMacros(); 530 | 531 | if (propertyGet.DeclaringType.HasGenericParameters) 532 | { 533 | propertyGet = propertyGet.MakeHostInstanceGeneric(propertyGet.DeclaringType.GenericParameters.Cast().ToArray()); 534 | } 535 | 536 | Instruction firstInstruction = methodDefinition.Body.Instructions.First(); 537 | 538 | ICollection returnInstructions = 539 | methodDefinition.Body.Instructions.ToList().Where(x => x.OpCode == OpCodes.Ret).ToList(); 540 | 541 | if (returnInstructions.Count == 0) 542 | { 543 | LogWarning(string.Format("Method {0} does not contain any return statement. Skip weaving of method {0}.", 544 | methodDefinition.Name)); 545 | return; 546 | } 547 | 548 | // Add local variables 549 | int cacheKeyIndex = methodDefinition.AddVariable(ModuleDefinition.TypeSystem.String); 550 | int resultIndex = methodDefinition.AddVariable(methodDefinition.ReturnType); 551 | int objectArrayIndex = methodDefinition.AddVariable(ModuleDefinition.TypeSystem.Object.MakeArrayType()); 552 | 553 | ILProcessor processor = methodDefinition.Body.GetILProcessor(); 554 | 555 | // Generate CacheKeyTemplate 556 | string cacheKey = CreateCacheKeyString(methodDefinition); 557 | 558 | Instruction current = firstInstruction.Prepend(processor.Create(OpCodes.Ldstr, cacheKey), processor); 559 | 560 | current = SetCacheKeyLocalVariable(current, methodDefinition, processor, cacheKeyIndex, objectArrayIndex); 561 | 562 | current = InjectCacheKeyCreatedCode(methodDefinition, current, processor, cacheKeyIndex); 563 | 564 | TypeDefinition propertyGetReturnTypeDefinition = propertyGet.ReturnType.Resolve(); 565 | 566 | if (!propertyGet.Resolve().IsStatic) 567 | { 568 | current = current.AppendLdarg(processor, 0); 569 | } 570 | 571 | MethodReference methodReferenceContain = 572 | methodDefinition.Module.Import(CacheTypeGetContainsMethod(propertyGetReturnTypeDefinition, 573 | CacheTypeContainsMethodName)); 574 | 575 | current = current 576 | .Append(processor.Create(OpCodes.Call, methodDefinition.Module.Import(propertyGet)), processor) 577 | .AppendLdloc(processor, cacheKeyIndex) 578 | .Append(processor.Create(OpCodes.Callvirt, methodReferenceContain), processor) 579 | .Append(processor.Create(OpCodes.Brfalse, firstInstruction), processor); 580 | 581 | // False branche (store value in cache of each return instruction) 582 | foreach (Instruction returnInstruction in returnInstructions) 583 | { 584 | returnInstruction.Previous.AppendStloc(processor, resultIndex); 585 | 586 | if (LogDebugOutput) 587 | { 588 | AppendDebugWrite(returnInstruction.Previous, processor, "Storing to cache."); 589 | } 590 | 591 | if (!propertyGet.Resolve().IsStatic) 592 | { 593 | returnInstruction.Previous.AppendLdarg(processor, 0); 594 | } 595 | 596 | MethodReference methodReferenceStore = 597 | methodDefinition.Module.Import(CacheTypeGetStoreMethod(propertyGetReturnTypeDefinition, CacheTypeStoreMethodName)); 598 | 599 | returnInstruction.Previous 600 | .Append(processor.Create(OpCodes.Call, methodDefinition.Module.Import(propertyGet)), processor) 601 | .AppendLdloc(processor, cacheKeyIndex) 602 | .AppendLdloc(processor, resultIndex) 603 | .AppendBoxIfNecessary(processor, methodDefinition.ReturnType); 604 | 605 | // Pass parameters to Store method if supported 606 | if (methodReferenceStore.Parameters.Count == 3) 607 | { 608 | returnInstruction.Previous 609 | .Append(processor.Create(OpCodes.Newobj, 610 | methodDefinition.Module.Import(References.DictionaryConstructor)), processor); 611 | 612 | foreach (CustomAttributeNamedArgument property in attribute.Properties.Union(attribute.Fields)) 613 | { 614 | returnInstruction.Previous 615 | .AppendDup(processor) 616 | .AppendLdstr(processor, property.Name) 617 | .AppendLd(processor, property.Argument, References) 618 | .AppendBoxIfNecessary(processor, 619 | property.Argument.Type != ModuleDefinition.TypeSystem.Object 620 | ? property.Argument.Type : ((CustomAttributeArgument)property.Argument.Value).Type) 621 | .Append(processor.Create(OpCodes.Callvirt, methodDefinition.Module.Import(References.DictionaryAddMethod)), 622 | processor); 623 | } 624 | } 625 | 626 | returnInstruction.Previous 627 | .Append(processor.Create(OpCodes.Callvirt, methodReferenceStore), processor) 628 | .AppendLdloc(processor, resultIndex); 629 | } 630 | 631 | if (LogDebugOutput) 632 | { 633 | current = AppendDebugWrite(current, processor, "Loading from cache."); 634 | } 635 | 636 | if (!propertyGet.Resolve().IsStatic) 637 | { 638 | current = current.AppendLdarg(processor, 0); 639 | } 640 | 641 | // Start of branche true 642 | MethodReference methodReferenceRetrieve = 643 | methodDefinition.Module.Import(CacheTypeGetRetrieveMethod(propertyGetReturnTypeDefinition, 644 | CacheTypeRetrieveMethodName)).MakeGeneric(new[] { methodDefinition.ReturnType }); 645 | 646 | current.Append(processor.Create(OpCodes.Call, methodDefinition.Module.Import(propertyGet)), processor) 647 | .AppendLdloc(processor, cacheKeyIndex) 648 | .Append(processor.Create(OpCodes.Callvirt, methodReferenceRetrieve), processor) 649 | .AppendStloc(processor, resultIndex) 650 | .Append(processor.Create(OpCodes.Br, returnInstructions.Last().Previous), processor); 651 | 652 | methodDefinition.Body.OptimizeMacros(); 653 | } 654 | 655 | private void WeaveMethod(MethodDefinition methodDefinition, CustomAttribute attribute) 656 | { 657 | MethodDefinition propertyGet = GetCacheGetter(methodDefinition); 658 | 659 | if (!IsMethodValidForWeaving(propertyGet, methodDefinition)) 660 | { 661 | return; 662 | } 663 | 664 | if (methodDefinition.ReturnType == methodDefinition.Module.TypeSystem.Void) 665 | { 666 | LogWarning(string.Format("Method {0} returns void. Skip weaving of method {0}.", methodDefinition.Name)); 667 | 668 | return; 669 | } 670 | 671 | LogInfo(string.Format("Weaving method {0}::{1}.", methodDefinition.DeclaringType.Name, methodDefinition.Name)); 672 | 673 | WeaveMethod(methodDefinition, attribute, propertyGet); 674 | } 675 | 676 | private void WeaveMethods(IEnumerable> methodDefinitions) 677 | { 678 | foreach (Tuple methodDefinition in methodDefinitions) 679 | { 680 | WeaveMethod(methodDefinition.Item1, methodDefinition.Item2); 681 | } 682 | } 683 | 684 | private void WeaveProperties(IEnumerable> properties) 685 | { 686 | foreach (Tuple propertyTuple in properties) 687 | { 688 | PropertyDefinition property = propertyTuple.Item1; 689 | CustomAttribute attribute = propertyTuple.Item2; 690 | 691 | // Get-Only Property, weave like normal methods 692 | if (property.SetMethod == null) 693 | { 694 | WeaveMethod(property.GetMethod, attribute); 695 | } 696 | else 697 | { 698 | MethodDefinition propertyGet = GetCacheGetter(property.SetMethod); 699 | 700 | if (!IsPropertySetterValidForWeaving(propertyGet, property.SetMethod)) 701 | { 702 | continue; 703 | } 704 | 705 | LogInfo(string.Format("Weaving property {0}::{1}.", property.DeclaringType.Name, property.Name)); 706 | 707 | WeaveMethod(property.GetMethod, attribute, propertyGet); 708 | WeavePropertySetter(property.SetMethod, propertyGet); 709 | } 710 | } 711 | } 712 | 713 | private void WeavePropertySetter(MethodDefinition setter, MethodReference propertyGet) 714 | { 715 | setter.Body.InitLocals = true; 716 | setter.Body.SimplifyMacros(); 717 | 718 | if (propertyGet.DeclaringType.HasGenericParameters) 719 | { 720 | propertyGet = propertyGet.MakeHostInstanceGeneric(propertyGet.DeclaringType.GenericParameters.Cast().ToArray()); 721 | } 722 | 723 | Instruction firstInstruction = setter.Body.Instructions.First(); 724 | ILProcessor processor = setter.Body.GetILProcessor(); 725 | 726 | // Add local variables 727 | int cacheKeyIndex = setter.AddVariable(ModuleDefinition.TypeSystem.String); 728 | 729 | // Generate CacheKeyTemplate 730 | string cacheKey = CreateCacheKeyString(setter); 731 | 732 | Instruction current = firstInstruction.Prepend(processor.Create(OpCodes.Ldstr, cacheKey), processor); 733 | 734 | // Create set cache key 735 | current = current.AppendStloc(processor, cacheKeyIndex); 736 | 737 | current = InjectCacheKeyCreatedCode(setter, current, processor, cacheKeyIndex); 738 | 739 | if (LogDebugOutput) 740 | { 741 | current = AppendDebugWrite(current, processor, "Clearing cache."); 742 | } 743 | 744 | if (!propertyGet.Resolve().IsStatic) 745 | { 746 | current = current.AppendLdarg(processor, 0); 747 | } 748 | 749 | current 750 | .Append(processor.Create(OpCodes.Call, setter.Module.Import(propertyGet)), processor) 751 | .AppendLdloc(processor, cacheKeyIndex) 752 | .Append(processor.Create(OpCodes.Callvirt, setter.Module.Import( 753 | CacheTypeGetRemoveMethod(propertyGet.ReturnType.Resolve(), CacheTypeRemoveMethodName))), processor); 754 | 755 | setter.Body.OptimizeMacros(); 756 | } 757 | } 758 | } --------------------------------------------------------------------------------