├── .gitignore ├── .hgignore ├── LICENSE ├── README.md └── src ├── ClaySharp.Tests ├── Behaviors │ ├── ArrayBehaviorTests.cs │ ├── ArrayPropAssignmentBehavior.cs │ └── ClayFactoryBehaviorTests.cs ├── BinderFallbackTests.cs ├── Binders │ └── InvokeBinderTests.cs ├── ClaySharp.Tests.csproj ├── ClayTests.cs ├── DefaultClayActivatorTests.cs ├── Implementation │ └── NamedArgumentsTests.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── ClaySharp.sln └── ClaySharp ├── Behaviors ├── ArrayBehavior.cs ├── ArrayFactoryBehavior.cs ├── ArrayPropAssignmentBehavior.cs ├── ClayFactoryBehavior.cs ├── InterfaceProxyBehavior.cs ├── NilBehavior.cs ├── NilResultBehavior.cs └── PropBehavior.cs ├── Clay.cs ├── ClayActivator.cs ├── ClayBehavior.cs ├── ClayBehaviorCollection.cs ├── ClayFactory.cs ├── ClayInteceptor.cs ├── ClayMetaObject.cs ├── ClaySharp.csproj ├── DefaultClayActivator.cs ├── IClayActivator.cs ├── IClayBehavior.cs ├── IClayBehaviorProvider.cs ├── ILogger.cs ├── Implementation └── Arguments.cs ├── Nil.cs ├── NullLogger.cs ├── Properties └── AssemblyInfo.cs └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/visualstudio,visualstudiocode,windows,macos 3 | 4 | ### VisualStudioCode ### 5 | .vscode 6 | 7 | 8 | 9 | ### Windows ### 10 | # Windows image file caches 11 | Thumbs.db 12 | ehthumbs.db 13 | 14 | # Folder config file 15 | Desktop.ini 16 | 17 | # Recycle Bin used on file shares 18 | $RECYCLE.BIN/ 19 | 20 | # Windows Installer files 21 | *.cab 22 | *.msi 23 | *.msm 24 | *.msp 25 | 26 | # Windows shortcuts 27 | *.lnk 28 | 29 | 30 | ### macOS ### 31 | *.DS_Store 32 | .AppleDouble 33 | .LSOverride 34 | 35 | # Icon must end with two \r 36 | Icon 37 | 38 | 39 | # Thumbnails 40 | ._* 41 | 42 | # Files that might appear in the root of a volume 43 | .DocumentRevisions-V100 44 | .fseventsd 45 | .Spotlight-V100 46 | .TemporaryItems 47 | .Trashes 48 | .VolumeIcon.icns 49 | .com.apple.timemachine.donotpresent 50 | 51 | # Directories potentially created on remote AFP share 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | 58 | 59 | ### VisualStudio ### 60 | ## Ignore Visual Studio temporary files, build results, and 61 | ## files generated by popular Visual Studio add-ons. 62 | 63 | # User-specific files 64 | *.suo 65 | *.user 66 | *.userosscache 67 | *.sln.docstates 68 | 69 | # User-specific files (MonoDevelop/Xamarin Studio) 70 | *.userprefs 71 | 72 | # Build results 73 | [Dd]ebug/ 74 | [Dd]ebugPublic/ 75 | [Rr]elease/ 76 | [Rr]eleases/ 77 | x64/ 78 | x86/ 79 | bld/ 80 | [Bb]in/ 81 | [Oo]bj/ 82 | [Ll]og/ 83 | 84 | # Visual Studio 2015 cache/options directory 85 | .vs/ 86 | # Uncomment if you have tasks that create the project's static files in wwwroot 87 | #wwwroot/ 88 | 89 | # MSTest test Results 90 | [Tt]est[Rr]esult*/ 91 | [Bb]uild[Ll]og.* 92 | 93 | # NUNIT 94 | *.VisualState.xml 95 | TestResult.xml 96 | 97 | # Build Results of an ATL Project 98 | [Dd]ebugPS/ 99 | [Rr]eleasePS/ 100 | dlldata.c 101 | 102 | # DNX 103 | project.lock.json 104 | project.fragment.lock.json 105 | artifacts/ 106 | 107 | *_i.c 108 | *_p.c 109 | *_i.h 110 | *.ilk 111 | *.meta 112 | *.obj 113 | *.pch 114 | *.pdb 115 | *.pgc 116 | *.pgd 117 | *.rsp 118 | *.sbr 119 | *.tlb 120 | *.tli 121 | *.tlh 122 | *.tmp 123 | *.tmp_proj 124 | *.log 125 | *.vspscc 126 | *.vssscc 127 | .builds 128 | *.pidb 129 | *.svclog 130 | *.scc 131 | 132 | # Chutzpah Test files 133 | _Chutzpah* 134 | 135 | # Visual C++ cache files 136 | ipch/ 137 | *.aps 138 | *.ncb 139 | *.opendb 140 | *.opensdf 141 | *.sdf 142 | *.cachefile 143 | *.VC.db 144 | *.VC.VC.opendb 145 | 146 | # Visual Studio profiler 147 | *.psess 148 | *.vsp 149 | *.vspx 150 | *.sap 151 | 152 | # TFS 2012 Local Workspace 153 | $tf/ 154 | 155 | # Guidance Automation Toolkit 156 | *.gpState 157 | 158 | # ReSharper is a .NET coding add-in 159 | _ReSharper*/ 160 | *.[Rr]e[Ss]harper 161 | *.DotSettings.user 162 | 163 | # JustCode is a .NET coding add-in 164 | .JustCode 165 | 166 | # TeamCity is a build add-in 167 | _TeamCity* 168 | 169 | # DotCover is a Code Coverage Tool 170 | *.dotCover 171 | 172 | # NCrunch 173 | _NCrunch_* 174 | .*crunch*.local.xml 175 | nCrunchTemp_* 176 | 177 | # MightyMoose 178 | *.mm.* 179 | AutoTest.Net/ 180 | 181 | # Web workbench (sass) 182 | .sass-cache/ 183 | 184 | # Installshield output folder 185 | [Ee]xpress/ 186 | 187 | # DocProject is a documentation generator add-in 188 | DocProject/buildhelp/ 189 | DocProject/Help/*.HxT 190 | DocProject/Help/*.HxC 191 | DocProject/Help/*.hhc 192 | DocProject/Help/*.hhk 193 | DocProject/Help/*.hhp 194 | DocProject/Help/Html2 195 | DocProject/Help/html 196 | 197 | # Click-Once directory 198 | publish/ 199 | 200 | # Publish Web Output 201 | *.[Pp]ublish.xml 202 | *.azurePubxml 203 | # TODO: Comment the next line if you want to checkin your web deploy settings 204 | # but database connection strings (with potential passwords) will be unencrypted 205 | *.pubxml 206 | *.publishproj 207 | 208 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 209 | # checkin your Azure Web App publish settings, but sensitive information contained 210 | # in these scripts will be unencrypted 211 | PublishScripts/ 212 | 213 | # NuGet Packages 214 | *.nupkg 215 | # The packages folder can be ignored because of Package Restore 216 | **/packages/* 217 | # except build/, which is used as an MSBuild target. 218 | !**/packages/build/ 219 | # Uncomment if necessary however generally it will be regenerated when needed 220 | #!**/packages/repositories.config 221 | # NuGet v3's project.json files produces more ignoreable files 222 | *.nuget.props 223 | *.nuget.targets 224 | 225 | # Microsoft Azure Build Output 226 | csx/ 227 | *.build.csdef 228 | 229 | # Microsoft Azure Emulator 230 | ecf/ 231 | rcf/ 232 | 233 | # Windows Store app package directories and files 234 | AppPackages/ 235 | BundleArtifacts/ 236 | Package.StoreAssociation.xml 237 | _pkginfo.txt 238 | 239 | # Visual Studio cache files 240 | # files ending in .cache can be ignored 241 | *.[Cc]ache 242 | # but keep track of directories ending in .cache 243 | !*.[Cc]ache/ 244 | 245 | # Others 246 | ClientBin/ 247 | ~$* 248 | *~ 249 | *.dbmdl 250 | *.dbproj.schemaview 251 | *.pfx 252 | *.publishsettings 253 | node_modules/ 254 | orleans.codegen.cs 255 | 256 | # Since there are multiple workflows, uncomment next line to ignore bower_components 257 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 258 | #bower_components/ 259 | 260 | # RIA/Silverlight projects 261 | Generated_Code/ 262 | 263 | # Backup & report files from converting an old project file 264 | # to a newer Visual Studio version. Backup files are not needed, 265 | # because we have git ;-) 266 | _UpgradeReport_Files/ 267 | Backup*/ 268 | UpgradeLog*.XML 269 | UpgradeLog*.htm 270 | 271 | # SQL Server files 272 | *.mdf 273 | *.ldf 274 | 275 | # Business Intelligence projects 276 | *.rdl.data 277 | *.bim.layout 278 | *.bim_*.settings 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | 289 | # Visual Studio 6 build log 290 | *.plg 291 | 292 | # Visual Studio 6 workspace options file 293 | *.opt 294 | 295 | # Visual Studio LightSwitch build output 296 | **/*.HTMLClient/GeneratedArtifacts 297 | **/*.DesktopClient/GeneratedArtifacts 298 | **/*.DesktopClient/ModelManifest.xml 299 | **/*.Server/GeneratedArtifacts 300 | **/*.Server/ModelManifest.xml 301 | _Pvt_Extensions 302 | 303 | # Paket dependency manager 304 | .paket/paket.exe 305 | paket-files/ 306 | 307 | # FAKE - F# Make 308 | .fake/ 309 | 310 | # JetBrains Rider 311 | .idea/ 312 | *.sln.iml -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | relre:_ReSharper* 2 | glob:obj 3 | glob:*.user 4 | glob:*.suo 5 | relre:src/.*/bin 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Microsoft Public License (Ms-PL) 2 | 3 | This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 4 | 5 | 1. Definitions 6 | 7 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. 8 | 9 | A "contribution" is the original software, or any additions or changes to the software. 10 | 11 | A "contributor" is any person that distributes its contribution under this license. 12 | 13 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 14 | 15 | 2. Grant of Rights 16 | 17 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 18 | 19 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 20 | 21 | 3. Conditions and Limitations 22 | 23 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 24 | 25 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 26 | 27 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 28 | 29 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 30 | 31 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Description 2 | Clay is a dynamic C# type that will enable you to sculpt objects of any shape just as easily as in JavaScript or other dynamic languages. 3 | 4 | # Blog Posts About Clay 5 | - [Clay: malleable C# dynamic objects – part 1: why we need it](http://weblogs.asp.net/bleroy/clay-malleable-c-dynamic-objects-part-1-why-we-need-it) 6 | - [Clay: malleable C# dynamic objects – part 2](http://weblogs.asp.net/bleroy/clay-malleable-c-dynamic-objects-part-2) 7 | 8 | # This Repo Description 9 | This is an import from the Clay project's [CodePlex repository](http://clay.codeplex.com) as it was on 2016/07/15. 10 | 11 | The purpose of this repo is to have a version that builds with modern dependencies and perhaps to make (what I consider) improvements. I did this because I've always admired Clay for a) it's flexibility and b) it's mind-bending implementation. :) 12 | 13 | The imported version is available as-is under the [canonical](https://github.com/jhorv/clay/tree/canonical) branch. 14 | 15 | Submit pull requests at will, this code is too cool to leave unused, or at the very least, unstudied! 16 | 17 | # License Notes 18 | Please note that this code retains the license from the CodePlex project - the [Microsoft Public License (Ms-PL)](https://opensource.org/licenses/MS-PL). 19 | 20 | Per [Wikipedia](https://en.wikipedia.org/wiki/Shared_source#Microsoft_Public_License_.28Ms-PL.29): 21 | 22 | > This is the least restrictive of the Microsoft licenses and allows for distribution of compiled code for either commercial or non-commercial purposes under any license that complies with the Ms-PL. Redistribution of the source code itself is permitted only under the Ms-PL. 23 | 24 | While I prefer the [MIT license](https://opensource.org/licenses/MIT), I expect the Ms-PL is permissive enough for most anyone. 25 | 26 | If the original authors choose to re-license to something even more permissive, that change will be reflected here. 27 | -------------------------------------------------------------------------------- /src/ClaySharp.Tests/Behaviors/ArrayBehaviorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using ClaySharp.Behaviors; 7 | using NUnit.Framework; 8 | 9 | namespace ClaySharp.Tests.Behaviors 10 | { 11 | [TestFixture] 12 | public class ArrayBehaviorTests 13 | { 14 | [Test] 15 | public void AddGrowsArray() 16 | { 17 | dynamic array = new Clay(new ArrayBehavior()); 18 | array.Add("Alpha"); 19 | array.Add("Beta"); 20 | 21 | Assert.That(array[0], Is.EqualTo("Alpha")); 22 | Assert.That(array[1], Is.EqualTo("Beta")); 23 | } 24 | 25 | 26 | [Test] 27 | public void LengthAndCountShowCurrentSize() 28 | { 29 | dynamic array = new Clay(new ArrayBehavior()); 30 | 31 | Assert.That(array.Length, Is.EqualTo(0)); 32 | Assert.That(array.Count, Is.EqualTo(0)); 33 | Assert.That(array.Length(), Is.EqualTo(0)); 34 | Assert.That(array.Count(), Is.EqualTo(0)); 35 | 36 | array.Add("Alpha"); 37 | array.Add("Beta"); 38 | 39 | Assert.That(array.Length, Is.EqualTo(2)); 40 | Assert.That(array.Count, Is.EqualTo(2)); 41 | Assert.That(array.Length(), Is.EqualTo(2)); 42 | Assert.That(array.Count(), Is.EqualTo(2)); 43 | } 44 | 45 | [Test] 46 | public void AddCallsCanBeChained() 47 | { 48 | dynamic array = new Clay(new ArrayBehavior()); 49 | array.Add("Alpha").Add("Beta"); 50 | 51 | Assert.That(array[0], Is.EqualTo("Alpha")); 52 | Assert.That(array[1], Is.EqualTo("Beta")); 53 | } 54 | 55 | [Test] 56 | public void AddTakesZeroOrMoreArguments() 57 | { 58 | dynamic array = new Clay(new ArrayBehavior()); 59 | array.Add().Add("Alpha").Add(null).Add("Beta", "Gamma", "Delta"); 60 | 61 | Assert.That(array[0], Is.EqualTo("Alpha")); 62 | Assert.That((object)array[1], Is.Null); 63 | Assert.That(array[2], Is.EqualTo("Beta")); 64 | Assert.That(array[3], Is.EqualTo("Gamma")); 65 | Assert.That(array[4], Is.EqualTo("Delta")); 66 | } 67 | 68 | 69 | /* 70 | * Insert(i,t)# 71 | * IndexOf(t)# 72 | * RemoveAt(i)# 73 | * [i]# 74 | * 75 | * Add(t)# 76 | * Clear() 77 | * Contains(t)# 78 | * CopyTo(arr, offs) 79 | * Remove(t)# 80 | * Count# 81 | * IsReadOnly 82 | * 83 | * GetEnumerator 84 | * 85 | * */ 86 | 87 | [Test] 88 | public void InsertAndRemoveAtIndexes() 89 | { 90 | dynamic array = new Clay(new ArrayBehavior()); 91 | array.Add("a", "b", "c", "d").Insert(2, "b++").RemoveAt(3); 92 | 93 | Assert.That(array.Count, Is.EqualTo(4)); 94 | Assert.That(array[0], Is.EqualTo("a")); 95 | Assert.That(array[1], Is.EqualTo("b")); 96 | Assert.That(array[2], Is.EqualTo("b++")); 97 | Assert.That(array[3], Is.EqualTo("d")); 98 | 99 | } 100 | 101 | [Test] 102 | public void InsertMayTakeSeveral() 103 | { 104 | dynamic array = new Clay(new ArrayBehavior()); 105 | array.Add("a", "b", "c", "d").Insert(2, "b2", "b3", "b4").RemoveAt(3); 106 | 107 | Assert.That(array.Count, Is.EqualTo(6)); 108 | Assert.That(array[0], Is.EqualTo("a")); 109 | Assert.That(array[1], Is.EqualTo("b")); 110 | Assert.That(array[2], Is.EqualTo("b2")); 111 | Assert.That(array[3], Is.EqualTo("b4")); 112 | Assert.That(array[4], Is.EqualTo("c")); 113 | Assert.That(array[5], Is.EqualTo("d")); 114 | } 115 | 116 | 117 | [Test] 118 | public void ContainsRemoveAndIndexOfFunctionAsNormalListWouldDictate() 119 | { 120 | dynamic array = new Clay(new ArrayBehavior()); 121 | array.Add("a", "b", "c", "d"); 122 | 123 | Assert.That(array.Contains("b"), Is.True); 124 | Assert.That(array.Contains("e"), Is.False); 125 | Assert.That(array.IndexOf("b"), Is.EqualTo(1)); 126 | Assert.That(array.IndexOf("e"), Is.EqualTo(-1)); 127 | Assert.That(array.Remove("b"), Is.True); 128 | Assert.That(array.Remove("e"), Is.False); 129 | 130 | Assert.That(array.Contains("b"), Is.False); 131 | Assert.That(array.IndexOf("b"), Is.EqualTo(-1)); 132 | Assert.That(array.Remove("b"), Is.False); 133 | } 134 | 135 | [Test] 136 | public void IteratingListReturnsValues() 137 | { 138 | dynamic array = new Clay(new ArrayBehavior(), new InterfaceProxyBehavior()); 139 | array.Add("a", "b", "c", "d"); 140 | 141 | var expectedCharacters = "abcd".GetEnumerator(); 142 | 143 | foreach (var item in array) 144 | { 145 | Assert.That(expectedCharacters.MoveNext(), Is.True); 146 | Assert.That(item, Is.EqualTo(expectedCharacters.Current.ToString())); 147 | } 148 | Assert.That(expectedCharacters.MoveNext(), Is.False); 149 | } 150 | 151 | [Test] 152 | public void CallingGetEnumeratorDirectlyOnDynamic() 153 | { 154 | dynamic array = new Clay(new ArrayBehavior(), new InterfaceProxyBehavior()); 155 | array.Add("hello"); 156 | 157 | IEnumerator enum1 = array.GetEnumerator(); 158 | Assert.That(enum1.MoveNext(), Is.True); 159 | Assert.That(enum1.Current, Is.EqualTo("hello")); 160 | Assert.That(enum1.MoveNext(), Is.False); 161 | 162 | IEnumerator enum2 = array.GetEnumerator(); 163 | Assert.That(enum2.MoveNext(), Is.True); 164 | Assert.That(enum2.Current, Is.EqualTo("hello")); 165 | Assert.That(enum2.MoveNext(), Is.False); 166 | } 167 | 168 | 169 | 170 | 171 | /* 172 | * Insert(i,t)# 173 | * IndexOf(t)# 174 | * RemoveAt(i)# 175 | * [i]# 176 | * 177 | * Add(t)# 178 | * Clear() 179 | * Contains(t)# 180 | * CopyTo(arr, offs) 181 | * Remove(t)# 182 | * Count# 183 | * IsReadOnly 184 | * 185 | * GetEnumerator 186 | * 187 | * */ 188 | 189 | [Test] 190 | public void UsingArrayWithVarietyOfCollectionInterfaces() 191 | { 192 | dynamic array = new Clay(new InterfaceProxyBehavior(), new ArrayBehavior()); 193 | 194 | array.Add("a", "b", "c", "d"); 195 | 196 | ICollection collectionString = array; 197 | 198 | Assert.That(collectionString.Contains("e"), Is.False); 199 | collectionString.Add("e"); 200 | Assert.That(collectionString.Contains("e"), Is.True); 201 | Assert.That(collectionString.Count, Is.EqualTo(5)); 202 | 203 | Assert.That(collectionString.Count(), Is.EqualTo(5)); 204 | 205 | 206 | IList listString = array; 207 | Assert.That(listString.IndexOf("b++"), Is.EqualTo(-1)); 208 | Assert.That(listString.IndexOf("c"), Is.EqualTo(2)); 209 | listString.Insert(2, "b++"); 210 | Assert.That(listString.IndexOf("b++"), Is.EqualTo(2)); 211 | Assert.That(listString.IndexOf("c"), Is.EqualTo(3)); 212 | listString.RemoveAt(2); 213 | Assert.That(listString.IndexOf("b++"), Is.EqualTo(-1)); 214 | Assert.That(listString.IndexOf("c"), Is.EqualTo(2)); 215 | 216 | Assert.That(listString[1], Is.EqualTo("b")); 217 | Assert.That(listString[2], Is.EqualTo("c")); 218 | } 219 | 220 | [Test] 221 | public void UsingViaSystemInterfacesWithLinqExtensionMethods() 222 | { 223 | dynamic array = new Clay(new InterfaceProxyBehavior(), new ArrayBehavior()); 224 | 225 | array.Add("a", "b", "c", "d", "e"); 226 | 227 | IEnumerable enumerableBase = array; 228 | ICollection collectionBase = array; 229 | IList listBase = array; 230 | 231 | IEnumerable enumerableObject = array; 232 | ICollection collectionObject = array; 233 | IList listObject = array; 234 | 235 | IEnumerable enumerableString = array; 236 | ICollection collectionString = array; 237 | IList listString = array; 238 | 239 | Assert.That(enumerableBase.Cast().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 240 | Assert.That(collectionBase.Cast().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 241 | Assert.That(listBase.Cast().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 242 | 243 | Assert.That(enumerableBase.OfType().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 244 | Assert.That(collectionBase.OfType().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 245 | Assert.That(listBase.OfType().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 246 | 247 | Assert.That(enumerableBase.Cast().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 248 | Assert.That(collectionBase.Cast().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 249 | Assert.That(listBase.Cast().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 250 | 251 | Assert.That(enumerableBase.OfType().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 252 | Assert.That(collectionBase.OfType().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 253 | Assert.That(listBase.OfType().Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 254 | 255 | Assert.That(enumerableObject.Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 256 | Assert.That(collectionObject.Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 257 | Assert.That(listObject.Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 258 | 259 | Assert.That(enumerableString.Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 260 | Assert.That(collectionString.Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 261 | Assert.That(listString.Reverse().Skip(1).Take(3).Last(), Is.EqualTo("b")); 262 | 263 | var enumerator = enumerableString.GetEnumerator(); 264 | Assert.That(enumerator.MoveNext(), Is.True); 265 | Assert.That(enumerator.Current, Is.EqualTo("a")); 266 | 267 | 268 | enumerator = collectionString.GetEnumerator(); 269 | Assert.That(enumerator.MoveNext(), Is.True); 270 | Assert.That(enumerator.Current, Is.EqualTo("a")); 271 | 272 | enumerator = listString.GetEnumerator(); 273 | Assert.That(enumerator.MoveNext(), Is.True); 274 | Assert.That(enumerator.Current, Is.EqualTo("a")); 275 | } 276 | 277 | [Test] 278 | public void ArrayCombinesWithOtherBehaviors() 279 | { 280 | dynamic combo = new Clay( 281 | new InterfaceProxyBehavior(), 282 | new PropBehavior(), 283 | new ArrayBehavior(), 284 | new NilResultBehavior()); 285 | 286 | Assert.That(combo.Count, Is.EqualTo(0)); 287 | 288 | combo.Hello = "world"; 289 | Assert.That(combo.Hello, Is.EqualTo("world")); 290 | Assert.That(combo.Hello(), Is.EqualTo("world")); 291 | Assert.That(combo["Hello"], Is.EqualTo("world")); 292 | 293 | Assert.That(combo.Count, Is.EqualTo(0)); 294 | 295 | combo.Add("alpha", "beta"); 296 | Assert.That(combo.Count, Is.EqualTo(2)); 297 | Assert.That(combo[0], Is.EqualTo("alpha")); 298 | Assert.That(combo[1], Is.EqualTo("beta")); 299 | 300 | var c2 = (ICombo)combo; 301 | Assert.That(c2.Hello, Is.EqualTo("world")); 302 | Assert.That(c2.Length, Is.EqualTo(2)); 303 | Assert.That(c2.Count, Is.EqualTo(2)); 304 | Assert.That(c2[0], Is.EqualTo("alpha")); 305 | Assert.That(c2[1], Is.EqualTo("beta")); 306 | Assert.That(c2.Again, Is.Null); 307 | 308 | Assert.That(c2.Extra.Title, Is.Null); 309 | Assert.That(c2.Extra.Description, Is.Null); 310 | 311 | Assert.That(c2.Aggregate(">", (a, b) => a + "(" + b + ")"), Is.EqualTo(">(alpha)(beta)")); 312 | } 313 | 314 | public interface ICombo : ICollection 315 | { 316 | string Hello { get; set; } 317 | string Again { get; set; } 318 | int Length { get; } 319 | object this[int index] { get; set; } 320 | ISafeNilResult Extra { get; set; } 321 | } 322 | 323 | public interface ISafeNilResult 324 | { 325 | string Title { get; set; } 326 | string Description { get; set; } 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /src/ClaySharp.Tests/Behaviors/ArrayPropAssignmentBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using ClaySharp.Behaviors; 5 | using NUnit.Framework; 6 | 7 | namespace ClaySharp.Tests.Behaviors 8 | { 9 | [TestFixture] 10 | public class ArrayPropAssignmentBehaviorTests 11 | { 12 | [Test] 13 | public void SingleArrayArgumentBecomesDynamic() 14 | { 15 | dynamic alpha = new Clay(new PropBehavior(), new ArrayPropAssignmentBehavior()); 16 | 17 | alpha 18 | .Names(new[] { "foo", "bar", "quad" }) 19 | .Places(new int[0]); 20 | 21 | alpha.Names.Add("quux"); 22 | alpha.Places.Add(4, 5, 6); 23 | 24 | IEnumerable names = alpha.Names; 25 | IEnumerable places = alpha.Places; 26 | 27 | Assert.That(names.Count(), Is.EqualTo(4)); 28 | Assert.That(names.Aggregate("|", (a, b) => a + b + "|"), Is.EqualTo("|foo|bar|quad|quux|")); 29 | Assert.That(places.Count(), Is.EqualTo(3)); 30 | Assert.That(places.Aggregate("|", (a, b) => a + b + "|"), Is.EqualTo("|4|5|6|")); 31 | } 32 | 33 | [Test] 34 | public void InvokeMemberWithSeveralArgumentsImpliesArrayInitialization() 35 | { 36 | dynamic alpha = new Clay(new PropBehavior(), new ArrayPropAssignmentBehavior()); 37 | 38 | alpha 39 | .Names("foo", "bar", "quad") 40 | .Places(4, 5, 6); 41 | 42 | alpha.Names.Add("quux"); 43 | 44 | IEnumerable names = alpha.Names; 45 | IEnumerable places = alpha.Places; 46 | 47 | Assert.That(names.Count(), Is.EqualTo(4)); 48 | Assert.That(names.Aggregate("|", (a, b) => a + b + "|"), Is.EqualTo("|foo|bar|quad|quux|")); 49 | Assert.That(places.Count(), Is.EqualTo(3)); 50 | Assert.That(places.Aggregate("|", (a, b) => a + b + "|"), Is.EqualTo("|4|5|6|")); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ClaySharp.Tests/Behaviors/ClayFactoryBehaviorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Dynamic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using System.Runtime.CompilerServices; 9 | using System.Text; 10 | using ClaySharp.Behaviors; 11 | using Microsoft.CSharp.RuntimeBinder; 12 | using NUnit.Framework; 13 | using Binder = Microsoft.CSharp.RuntimeBinder.Binder; 14 | 15 | namespace ClaySharp.Tests.Behaviors 16 | { 17 | [TestFixture] 18 | public class ClayFactoryBehaviorTests 19 | { 20 | [Test] 21 | public void InvokingMethodsCreateDynamicObjectWithBehaviors() 22 | { 23 | dynamic factory = new Clay(new ClayFactoryBehavior()); 24 | object alpha = factory.Alpha(); 25 | 26 | Assert.That(alpha, Is.Not.Null); 27 | Assert.That(alpha, Is.AssignableTo()); 28 | Assert.That(alpha, Is.AssignableTo()); 29 | } 30 | 31 | [Test] 32 | public void DifferentInstanceCreatedEachCall() 33 | { 34 | dynamic factory = new Clay(new ClayFactoryBehavior()); 35 | object alpha1 = factory.Alpha(); 36 | object alpha2 = factory.Alpha(); 37 | 38 | Assert.That(alpha1, Is.Not.SameAs(alpha2)); 39 | } 40 | 41 | [Test] 42 | public void FactoryMethodCopiesPropertiesOfOptionalArgument() 43 | { 44 | dynamic factory = new Clay(new ClayFactoryBehavior()); 45 | var alpha = factory.Alpha(new { One = 1, Two = "dos" }); 46 | Assert.That(alpha.One, Is.EqualTo(1)); 47 | Assert.That(alpha.Two, Is.EqualTo("dos")); 48 | } 49 | 50 | [Test] 51 | public void FactoryMethodUsesNamedParameters() 52 | { 53 | dynamic factory = new Clay(new ClayFactoryBehavior()); 54 | 55 | var alpha = factory.Alpha(new { Red = "#f00" }, One: 1, Two: "dos"); 56 | 57 | Assert.That(alpha.One, Is.EqualTo(1)); 58 | Assert.That(alpha.Two, Is.EqualTo("dos")); 59 | Assert.That(alpha.Red, Is.EqualTo("#f00")); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ClaySharp.Tests/BinderFallbackTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using ClaySharp.Behaviors; 6 | using Microsoft.CSharp.RuntimeBinder; 7 | using NUnit.Framework; 8 | 9 | namespace ClaySharp.Tests 10 | { 11 | [TestFixture] 12 | public class BinderFallbackTests 13 | { 14 | 15 | class TestMemberBehavior : ClayBehavior 16 | { 17 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 18 | { 19 | return name == "Sample" ? "Data" : proceed(); 20 | } 21 | public override object GetMember(Func proceed, object self, string name) 22 | { 23 | return name == "Sample" ? "Data" : proceed(); 24 | } 25 | public override object SetMember(Func proceed, object self, string name, object value) 26 | { 27 | return name == "Sample" ? "Data" : proceed(); 28 | } 29 | } 30 | 31 | class TestIndexBehavior : ClayBehavior 32 | { 33 | public override object GetIndex(Func proceed, object self, IEnumerable keys) 34 | { 35 | return IsIndexZero(keys) ? "Data" : proceed(); 36 | } 37 | 38 | public override object SetIndex(Func proceed, object self, IEnumerable keys, object value) 39 | { 40 | return IsIndexZero(keys) ? "Data" : proceed(); 41 | } 42 | 43 | private static bool IsIndexZero(IEnumerable keys) 44 | { 45 | return keys.Count() == 1 46 | && keys.Single().GetType() == typeof(int) 47 | && keys.Cast().Single() == 0; 48 | } 49 | } 50 | 51 | 52 | [Test] 53 | public void InvokeMemberThrowsFallbackException() 54 | { 55 | dynamic alpha = new Object(); 56 | dynamic beta = new Clay(new TestMemberBehavior()); 57 | 58 | var ex1 = Assert.Throws(() => alpha.Hello1()); 59 | 60 | Assert.That(ex1.Message, Does.EndWith("does not contain a definition for 'Hello1'")); 61 | 62 | var ex2 = Assert.Throws(() => beta.Hello2()); 63 | 64 | Assert.That(ex2.Message, Does.EndWith("does not contain a definition for 'Hello2'")); 65 | 66 | Assert.That(beta.Sample(), Is.EqualTo("Data")); 67 | 68 | } 69 | 70 | 71 | [Test] 72 | public void GetMemberThrowsFallbackException() 73 | { 74 | dynamic alpha = new Object(); 75 | dynamic beta = new Clay(new TestMemberBehavior()); 76 | 77 | var ex1 = Assert.Throws(() => { var hi = alpha.Hello1; }); 78 | 79 | Assert.That(ex1.Message, Does.EndWith("does not contain a definition for 'Hello1'")); 80 | 81 | var ex2 = Assert.Throws(() => { var hi = beta.Hello2; }); 82 | 83 | Assert.That(ex2.Message, Does.EndWith("does not contain a definition for 'Hello2'")); 84 | 85 | Assert.That(beta.Sample, Is.EqualTo("Data")); 86 | } 87 | 88 | [Test] 89 | public void SetMemberThrowsFallbackException() 90 | { 91 | dynamic alpha = new Object(); 92 | dynamic beta = new Clay(new TestMemberBehavior()); 93 | 94 | var ex1 = Assert.Throws(() => { alpha.Hello1 = 1; }); 95 | 96 | Assert.That(ex1.Message, Does.EndWith("does not contain a definition for 'Hello1'")); 97 | 98 | var ex2 = Assert.Throws(() => { beta.Hello2 = 2; }); 99 | 100 | Assert.That(ex2.Message, Does.EndWith("does not contain a definition for 'Hello2'")); 101 | 102 | var x = (beta.Sample = 3); 103 | Assert.That(x, Is.EqualTo("Data")); 104 | } 105 | 106 | 107 | [Test] 108 | public void GetIndexThrowsFallbackException() 109 | { 110 | dynamic alpha = new Object(); 111 | dynamic beta = new Clay(new TestMemberBehavior()); 112 | 113 | var ex1 = Assert.Throws(() => { var hi = alpha[0]; }); 114 | Assert.That(ex1.Message, Does.Match(@"Cannot apply indexing with \[\] to an expression of type .*")); 115 | 116 | var ex2 = Assert.Throws(() => { var hi = beta[0]; }); 117 | Assert.That(ex2.Message, Does.Match(@"Cannot apply indexing with \[\] to an expression of type .*")); 118 | } 119 | 120 | 121 | [Test] 122 | public void SetIndexThrowsFallbackException() 123 | { 124 | dynamic alpha = new Object(); 125 | dynamic beta = new Clay(new TestMemberBehavior()); 126 | 127 | var ex1 = Assert.Throws(() => { alpha[0] = 1; }); 128 | 129 | Assert.That(ex1.Message, Does.Match(@"Cannot apply indexing with \[\] to an expression of type .*")); 130 | 131 | var ex2 = Assert.Throws(() => { beta[0] = 2; }); 132 | 133 | Assert.That(ex2.Message, Does.Match(@"Cannot apply indexing with \[\] to an expression of type .*")); 134 | 135 | } 136 | 137 | public interface IAlpha 138 | { 139 | string Hello(); 140 | string Foo(); 141 | } 142 | public class Alpha 143 | { 144 | public virtual string Hello() 145 | { 146 | return "World"; 147 | } 148 | } 149 | 150 | public class AlphaBehavior : ClayBehavior 151 | { 152 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 153 | { 154 | return proceed() + "-"; 155 | } 156 | public override object InvokeMemberMissing(Func proceed, object self, string name, INamedEnumerable args) 157 | { 158 | if (name == "Foo") 159 | return "Bar"; 160 | return proceed(); 161 | } 162 | } 163 | 164 | [Test] 165 | public void TestInvokePaths() 166 | { 167 | var dynamically = ClayActivator.CreateInstance(new IClayBehavior[] { 168 | new InterfaceProxyBehavior(), 169 | new AlphaBehavior() 170 | }); 171 | Alpha statically = dynamically; 172 | IAlpha interfacially = dynamically; 173 | 174 | Assert.That(dynamically.Hello(), Is.EqualTo("World-")); 175 | Assert.That(statically.Hello(), Is.EqualTo("World-")); 176 | Assert.That(interfacially.Hello(), Is.EqualTo("World-")); 177 | 178 | Assert.That(dynamically.Foo(), Is.EqualTo("Bar-")); 179 | Assert.That(interfacially.Foo(), Is.EqualTo("Bar-")); 180 | 181 | Assert.Throws(() => dynamically.MissingNotHandled()); 182 | } 183 | 184 | 185 | public class Beta 186 | { 187 | public virtual string Hello 188 | { 189 | get { return "World"; } 190 | } 191 | } 192 | 193 | public class BetaBehavior : ClayBehavior 194 | { 195 | public override object GetMember(Func proceed, object self, string name) 196 | { 197 | return proceed() + "-"; 198 | } 199 | public override object GetMemberMissing(Func proceed, object self, string name) 200 | { 201 | if (name == "Foo") 202 | return "Bar"; 203 | return proceed(); 204 | } 205 | } 206 | 207 | [Test] 208 | public void TestGetPaths() 209 | { 210 | var dynamically = ClayActivator.CreateInstance(new[] { new BetaBehavior() }); 211 | Beta statically = dynamically; 212 | 213 | Assert.That(dynamically.Hello, Is.EqualTo("World-")); 214 | Assert.That(statically.Hello, Is.EqualTo("World-")); 215 | 216 | Assert.That(dynamically.Foo, Is.EqualTo("Bar-")); 217 | 218 | Assert.Throws(() => { var x = dynamically.MissingPropNotHandled; }); 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/ClaySharp.Tests/Binders/InvokeBinderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using NUnit.Framework; 6 | 7 | namespace ClaySharp.Tests.Binders 8 | { 9 | [TestFixture] 10 | public class InvokeBinderTests 11 | { 12 | [Test] 13 | public void InvokeMemberContainsName() 14 | { 15 | dynamic clay = new Clay(new TestBehavior()); 16 | var result = clay.Hello(); 17 | Assert.That(result, Does.Contain("[name:Hello]")); 18 | Assert.That(result, Does.Contain("[count:0]")); 19 | } 20 | [Test] 21 | public void InvokeBinder() 22 | { 23 | dynamic clay = new Clay(new TestBehavior()); 24 | var result = clay(); 25 | Assert.That(result, Does.Contain("[name:]")); 26 | Assert.That(result, Does.Contain("[count:0]")); 27 | } 28 | 29 | class TestBehavior : ClayBehavior 30 | { 31 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 32 | { 33 | return string.Format("[name:{0}] [count:{1}]", name ?? "", args.Count()); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ClaySharp.Tests/ClaySharp.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {10369238-A590-48BF-8D3E-E83EB6F0C931} 9 | Library 10 | Properties 11 | ClaySharp.Tests 12 | ClaySharp.Tests 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 | ..\packages\NUnit.3.4.1\lib\net40\nunit.framework.dll 36 | True 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | {76BCD43B-7BA5-4B63-B1E1-861641CA2686} 60 | ClaySharp 61 | 62 | 63 | 64 | 65 | 66 | 67 | 74 | -------------------------------------------------------------------------------- /src/ClaySharp.Tests/ClayTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Dynamic; 4 | using System.Linq.Expressions; 5 | using ClaySharp.Behaviors; 6 | using NUnit.Framework; 7 | 8 | namespace ClaySharp.Tests 9 | { 10 | 11 | 12 | [TestFixture] 13 | public class ClayTests 14 | { 15 | public ClayHelper S { get; set; } 16 | public dynamic New { get; set; } 17 | 18 | [SetUp] 19 | public void Init() 20 | { 21 | S = new ClayHelper(); 22 | New = new ClayFactory(); 23 | } 24 | 25 | static IEnumerable Cat(T t) 26 | { 27 | yield return t; 28 | } 29 | 30 | [Test] 31 | public void PropertiesCanBeAssignedAndRetrieved() 32 | { 33 | dynamic shape = new Clay(Cat(new PropBehavior())); 34 | shape.Foo = "textbox"; 35 | Assert.That((object)shape.Foo, Is.EqualTo("textbox")); 36 | } 37 | 38 | public interface ITest 39 | { 40 | string Foo { get; set; } 41 | ITestSub Bar { get; set; } 42 | } 43 | 44 | public interface ITestSub 45 | { 46 | string FooSub { get; set; } 47 | } 48 | 49 | 50 | [Test] 51 | public void TypeCastToInterface() 52 | { 53 | dynamic shape = new Clay(new PropBehavior(), new NilResultBehavior(), new InterfaceProxyBehavior()); 54 | dynamic shape2 = new Clay(new PropBehavior(), new NilResultBehavior(), new InterfaceProxyBehavior()); 55 | 56 | var test0 = shape is ITest; 57 | var test1 = shape as ITest; 58 | var test2 = (ITest)shape; 59 | ITest test3 = shape; 60 | Assert.That(test0, Is.False); 61 | Assert.That(test1, Is.Null); 62 | Assert.That(test2, Is.Not.Null); 63 | Assert.That(test3, Is.Not.Null); 64 | 65 | Assert.That((string)shape.Foo, Is.Null); 66 | Assert.That(test2.Foo, Is.Null); 67 | shape.Foo = "Bar"; 68 | Assert.That(shape.Foo, Is.EqualTo("Bar")); 69 | Assert.That(test2.Foo, Is.EqualTo("Bar")); 70 | test2.Foo = "Quux"; 71 | Assert.That(shape.Foo, Is.EqualTo("Quux")); 72 | Assert.That(test2.Foo, Is.EqualTo("Quux")); 73 | 74 | Assert.That(test2.Bar, Is.Not.Null); 75 | Assert.That(shape.Bar, Is.SameAs(Nil.Instance)); 76 | Assert.That(test2.Bar.FooSub, Is.Null); 77 | Assert.That(shape.Bar.FooSub, Is.SameAs(Nil.Instance)); 78 | 79 | test2.Bar = shape2; 80 | 81 | Assert.That(test2.Bar, Is.Not.Null); 82 | Assert.That(shape.Bar, Is.Not.Null); 83 | 84 | Assert.That(test2.Bar.FooSub, Is.Null); 85 | Assert.That(shape.Bar.FooSub, Is.SameAs(Nil.Instance)); 86 | 87 | shape2.FooSub = "Yarg"; 88 | Assert.That(test2.Bar.FooSub, Is.EqualTo("Yarg")); 89 | Assert.That(shape.Bar.FooSub, Is.EqualTo("Yarg")); 90 | 91 | } 92 | 93 | 94 | public interface ITestForm 95 | { 96 | string ShapeName(); 97 | object this[object key] { get; set; } 98 | 99 | ITestActions Actions { get; set; } 100 | int? Misc { get; set; } 101 | } 102 | 103 | public interface ITestActions 104 | { 105 | string ShapeName { get; set; } 106 | IButton Save { get; set; } 107 | IButton Cancel { get; set; } 108 | IButton Preview { get; set; } 109 | } 110 | 111 | public interface IButton 112 | { 113 | string ShapeName { get; set; } 114 | string Id { get; set; } 115 | string Value { get; set; } 116 | } 117 | 118 | [Test] 119 | public void CreateSyntax() 120 | { 121 | 122 | var form = New.Form(new { Misc = 4 }) 123 | .Actions(New.Fieldset() 124 | .Save(New.Button().Value("Save").Id("Hello")) 125 | .Cancel(New.Button().Value("Cancel"))); 126 | 127 | 128 | var bar = New.Foo(new { Bleah = (object)null }); 129 | 130 | Assert.That(bar.Bleah(), Is.SameAs(Nil.Instance)); 131 | Assert.That(bar.Bleah, Is.SameAs(Nil.Instance)); 132 | Assert.That(bar.Yarg, Is.SameAs(Nil.Instance)); 133 | Assert.That(bar.One.Two.Three()["Four"], Is.SameAs(Nil.Instance)); 134 | 135 | var foo1 = bar.Foo; 136 | string foo2 = bar.Foo; 137 | int? foo3 = bar.Foo; 138 | var foo4 = bar.Foo == null; 139 | var foo5 = bar.Foo != null; 140 | var foo6 = (ITest)bar.Foo; 141 | var foo7 = (string)bar.Foo ?? "yarg"; 142 | 143 | // var foo8 = bar.Foo ? bar.Foo : (dynamic)"yarg"; 144 | 145 | 146 | Assert.That(foo1, Is.SameAs(Nil.Instance)); 147 | Assert.That(foo2, Is.Null); 148 | Assert.That(foo3, Is.Null); 149 | Assert.That(foo4, Is.True); 150 | Assert.That(foo5, Is.False); 151 | Assert.That(foo6, Is.Not.Null); 152 | Assert.That(foo7, Is.EqualTo("yarg")); 153 | //Assert.That(foo8, Is.EqualTo("yarg")); 154 | 155 | // form.Actions += bar; 156 | 157 | form.Misc += 3; 158 | 159 | Assert.That(form.ShapeName, Is.EqualTo("Form")); 160 | Assert.That(form.Misc, Is.EqualTo(7)); 161 | Assert.That(form.Actions.Save.Id, Is.EqualTo("Hello")); 162 | Assert.That(form.Actions.Save.Value, Is.EqualTo("Save")); 163 | Assert.That(form.Actions.Cancel.Value, Is.EqualTo("Cancel")); 164 | 165 | Assert.That(form.Misc(), Is.EqualTo(7)); 166 | Assert.That(form.Actions().Save().Id(), Is.EqualTo("Hello")); 167 | Assert.That(form.Actions().Save().Value(), Is.EqualTo("Save")); 168 | Assert.That(form.Actions().Cancel().Value(), Is.EqualTo("Cancel")); 169 | 170 | form[3] = "hello"; 171 | 172 | Assert.That(form["Misc"], Is.EqualTo(7)); 173 | Assert.That(form["Actions"]["Save"]["Id"], Is.EqualTo("Hello")); 174 | Assert.That(form["Actions"]["Save"]["Value"], Is.EqualTo("Save")); 175 | Assert.That(form["Actions"]["Cancel"]["Value"], Is.EqualTo("Cancel")); 176 | 177 | ITestForm f = form; 178 | Assert.That(f.Misc, Is.EqualTo(7)); 179 | Assert.That(f.Actions.ShapeName, Is.EqualTo("Fieldset")); 180 | Assert.That(f.Actions.Save.Id, Is.EqualTo("Hello")); 181 | Assert.That(f.Actions.Save.Value, Is.EqualTo("Save")); 182 | Assert.That(f.Actions.Cancel.Value, Is.EqualTo("Cancel")); 183 | Assert.That(f.Actions.Preview.Id, Is.Null); 184 | Assert.That((dynamic)f.Actions.Preview == null); 185 | 186 | Assert.That(f["Misc"], Is.EqualTo(7)); 187 | f["Misc"] = 4; 188 | Assert.That(f.Misc, Is.EqualTo(4)); 189 | Assert.That(f["Misc"], Is.EqualTo(4)); 190 | Assert.That(form.Misc, Is.EqualTo(4)); 191 | 192 | f.Misc = 9; 193 | Assert.That(f.Misc, Is.EqualTo(9)); 194 | Assert.That(f["Misc"], Is.EqualTo(9)); 195 | Assert.That(form.Misc, Is.EqualTo(9)); 196 | } 197 | 198 | [Test] 199 | public void CreateArraySyntax() 200 | { 201 | var directory = New.Array( 202 | New.Person().Name("Louis").Aliases(new[] { "Lou" }), 203 | New.Person().Name("Bertrand").Aliases("bleroy", "boudin") 204 | ).Name("Orchard folks"); 205 | 206 | Assert.That(directory.Count, Is.EqualTo(2)); 207 | Assert.That(directory.Name, Is.EqualTo("Orchard folks")); 208 | Assert.That(directory[0].Name, Is.EqualTo("Louis")); 209 | Assert.That(directory[0].Aliases.Count, Is.EqualTo(1)); 210 | Assert.That(directory[0].Aliases[0], Is.EqualTo("Lou")); 211 | Assert.That(directory[1].Name, Is.EqualTo("Bertrand")); 212 | Assert.That(directory[1].Aliases.Count, Is.EqualTo(2)); 213 | Assert.That(directory[1].Aliases[0], Is.EqualTo("bleroy")); 214 | Assert.That(directory[1].Aliases[1], Is.EqualTo("boudin")); 215 | } 216 | 217 | public interface IPerson 218 | { 219 | string FirstName { get; set; } 220 | string LastName { get; set; } 221 | } 222 | 223 | [Test] 224 | public void BertrandsAssumptions() 225 | { 226 | var pentagon = New.Shape(); 227 | pentagon["FavoriteNumber"] = 5; 228 | 229 | Assert.That(pentagon.FavoriteNumber, Is.EqualTo(5)); 230 | Assert.That(pentagon.SomethingNeverSet, Is.SameAs(Nil.Instance)); 231 | 232 | var person = New.Person() 233 | .FirstName("Louis") 234 | .LastName("Dejardin"); 235 | Assert.That(person.FirstName, Is.EqualTo("Louis")); 236 | Assert.That(person["FirstName"], Is.EqualTo("Louis")); 237 | Assert.That(person.FirstName(), Is.EqualTo("Louis")); 238 | Assert.That(person.LastName, Is.EqualTo("Dejardin")); 239 | 240 | var otherPerson = New.Person(new 241 | { 242 | FirstName = "Bertrand", 243 | LastName = "Le Roy" 244 | }); 245 | Assert.That(otherPerson.FirstName, Is.EqualTo("Bertrand")); 246 | Assert.That(otherPerson.LastName, Is.EqualTo("Le Roy")); 247 | 248 | var yetAnotherPerson = New.Person( 249 | FirstName: "Renaud", 250 | LastName: "Paquay" 251 | ); 252 | Assert.That(yetAnotherPerson.FirstName, Is.EqualTo("Renaud")); 253 | Assert.That(yetAnotherPerson.LastName, Is.EqualTo("Paquay")); 254 | 255 | var people = New.Array( 256 | New.Person().FirstName("Louis").LastName("Dejardin"), 257 | New.Person().FirstName("Bertrand").LastName("Le Roy") 258 | ); 259 | Assert.That(people.Count, Is.EqualTo(2)); 260 | Assert.That(people[0].FirstName, Is.EqualTo("Louis")); 261 | Assert.That(people[1].FirstName, Is.EqualTo("Bertrand")); 262 | Assert.That(people[0].LastName, Is.EqualTo("Dejardin")); 263 | Assert.That(people[1].LastName, Is.EqualTo("Le Roy")); 264 | 265 | var a = ""; 266 | foreach (var p in people) 267 | { 268 | a += p.FirstName + "|"; 269 | } 270 | Assert.That(a, Is.EqualTo("Louis|Bertrand|")); 271 | 272 | otherPerson.Aliases("bleroy", "BoudinFatal"); 273 | Assert.That(otherPerson.Aliases.Count, Is.EqualTo(2)); 274 | 275 | person.Aliases(new[] { "Lou" }); 276 | Assert.That(person.Aliases.Count, Is.EqualTo(1)); 277 | person.Aliases.Add("loudej"); 278 | Assert.That(person.Aliases.Count, Is.EqualTo(2)); 279 | 280 | IPerson lou = people[0]; 281 | var fullName = lou.FirstName + " " + lou.LastName; 282 | Assert.That(fullName, Is.EqualTo("Louis Dejardin")); 283 | } 284 | 285 | [Test] 286 | public void ShapeFactorySetsShapeName() 287 | { 288 | var x1 = New.Something(); 289 | var x2 = New.SomethingElse(); 290 | 291 | Assert.That(x1.ShapeName, Is.EqualTo("Something")); 292 | Assert.That(x2.ShapeName, Is.EqualTo("SomethingElse")); 293 | } 294 | 295 | [Test] 296 | public void OptionsArgumentSetsProperties() 297 | { 298 | var x = New.Something(new { One = "1", Two = 2 }); 299 | 300 | Assert.That(x.ShapeName, Is.EqualTo("Something")); 301 | Assert.That(x.One, Is.EqualTo("1")); 302 | Assert.That(x.Two, Is.EqualTo(2)); 303 | } 304 | 305 | [Test] 306 | public void ClayMetaObjectCanBeUsedAsAnInternalImplementationDetail() 307 | { 308 | dynamic augmented = new AugmentedObject(); 309 | Assert.That(augmented.Foo, Is.EqualTo("Bar")); 310 | } 311 | 312 | public class AugmentedObject : IDynamicMetaObjectProvider 313 | { 314 | 315 | public DynamicMetaObject GetMetaObject(Expression parameter) 316 | { 317 | return new ClayMetaObject(this, parameter, ex => Expression.Constant(new FooIsBar(), typeof(IClayBehavior))); 318 | } 319 | 320 | class FooIsBar : ClayBehavior 321 | { 322 | public override object GetMemberMissing(Func proceed, object self, string name) 323 | { 324 | return name == "Foo" ? "Bar" : base.GetMemberMissing(proceed, self, name); 325 | } 326 | } 327 | } 328 | } 329 | 330 | public class ClayHelper 331 | { 332 | public dynamic New(string shapeName, Action initialize) 333 | { 334 | var item = new Clay(new PropBehavior()); 335 | initialize(item); 336 | return item; 337 | } 338 | public dynamic New(string shapeName) 339 | { 340 | return New(shapeName, item => { }); 341 | } 342 | } 343 | 344 | 345 | 346 | public static class ClayHelperExtensions 347 | { 348 | public static dynamic TextBox(this ClayHelper clayHelper, Action initialize) 349 | { 350 | return clayHelper.New("textbox", initialize); 351 | } 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/ClaySharp.Tests/DefaultClayActivatorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using ClaySharp.Behaviors; 6 | using NUnit.Framework; 7 | 8 | namespace ClaySharp.Tests 9 | { 10 | [TestFixture] 11 | public class DefaultClayActivatorTests 12 | { 13 | private IClayActivator _activator; 14 | 15 | [SetUp] 16 | public void Init() 17 | { 18 | _activator = new DefaultClayActivator(); 19 | ClayActivator.ServiceLocator = () => _activator; 20 | } 21 | 22 | [Test] 23 | public void SimpleActivationUsesDefaultClass() 24 | { 25 | var alpha = ClayActivator.CreateInstance(Enumerable.Empty()); 26 | var type = alpha.GetType(); 27 | 28 | Assert.That(type, Is.EqualTo(typeof(Clay))); 29 | } 30 | 31 | public class ClayPlus : Clay 32 | { 33 | public ClayPlus(IEnumerable behaviors) 34 | : base(behaviors) 35 | { 36 | } 37 | 38 | public virtual string Hello { get { return "World"; } } 39 | 40 | public virtual int Add(int left, int right) 41 | { 42 | return left + right; 43 | } 44 | } 45 | 46 | public interface IClayPlus 47 | { 48 | string Hello { get; } 49 | int Add(int left, int right); 50 | } 51 | 52 | [Test] 53 | public void ClaySubclassIsActivatedWithoutDynamicProxy() 54 | { 55 | var alpha = ClayActivator.CreateInstance(Enumerable.Empty()); 56 | var type = alpha.GetType(); 57 | 58 | Assert.That(type, Is.EqualTo(typeof(ClayPlus))); 59 | } 60 | 61 | [Test] 62 | public void SubclassMembersRemainAvailableStaticallyAndDynamicallyAndViaInterface() 63 | { 64 | 65 | var alpha = ClayActivator.CreateInstance(new[] { new InterfaceProxyBehavior() }); 66 | 67 | dynamic dynamically = alpha; 68 | ClayPlus statically = alpha; 69 | IClayPlus interfacially = alpha; 70 | 71 | Assert.That(dynamically.Hello, Is.EqualTo("World")); 72 | Assert.That(statically.Hello, Is.EqualTo("World")); 73 | Assert.That(interfacially.Hello, Is.EqualTo("World")); 74 | 75 | Assert.That(dynamically.Add(3, 4), Is.EqualTo(7)); 76 | Assert.That(statically.Add(3, 4), Is.EqualTo(7)); 77 | Assert.That(interfacially.Add(3, 4), Is.EqualTo(7)); 78 | Assert.That(interfacially.Add(3, 5), Is.EqualTo(8)); 79 | Assert.That(interfacially.Add(3, 6), Is.EqualTo(9)); 80 | } 81 | 82 | 83 | public class Anything 84 | { 85 | private readonly string _helloText; 86 | 87 | public Anything() 88 | { 89 | } 90 | public Anything(string helloText) 91 | { 92 | _helloText = helloText; 93 | } 94 | 95 | public virtual string Hello { get { return _helloText ?? "World"; } } 96 | 97 | public virtual int Add(int left, int right) 98 | { 99 | return left + right; 100 | } 101 | } 102 | 103 | [Test] 104 | public void ClaySubclassFromAnythingIsActivatedDynamixProxyAddingDlrInterfaces() 105 | { 106 | var alpha = ClayActivator.CreateInstance(Enumerable.Empty()); 107 | 108 | var type = alpha.GetType(); 109 | 110 | Assert.That(type, Is.Not.EqualTo(typeof(Anything))); 111 | Assert.That(typeof(Anything).IsAssignableFrom(type)); 112 | 113 | } 114 | 115 | [Test] 116 | public void SubclassFromAnythingMembersRemainAvailableStaticallyAndDynamicallyAndViaInterface() 117 | { 118 | var alpha = ClayActivator.CreateInstance(new[] { new InterfaceProxyBehavior() }); 119 | var type = alpha.GetType(); 120 | Assert.That(type, Is.Not.EqualTo(typeof(Anything))); 121 | 122 | dynamic dynamically = alpha; 123 | Anything statically = alpha; 124 | IClayPlus interfacially = alpha; 125 | 126 | Assert.That(dynamically.Hello, Is.EqualTo("World")); 127 | Assert.That(statically.Hello, Is.EqualTo("World")); 128 | Assert.That(interfacially.Hello, Is.EqualTo("World")); 129 | 130 | Assert.That(dynamically.Add(3, 4), Is.EqualTo(7)); 131 | Assert.That(statically.Add(3, 4), Is.EqualTo(7)); 132 | Assert.That(interfacially.Add(3, 4), Is.EqualTo(7)); 133 | Assert.That(interfacially.Add(3, 5), Is.EqualTo(8)); 134 | Assert.That(interfacially.Add(3, 6), Is.EqualTo(9)); 135 | } 136 | 137 | 138 | [Test] 139 | public void BehaviorsCanFilterVirtualMethods() 140 | { 141 | 142 | var alpha = ClayActivator.CreateInstance(new IClayBehavior[] { 143 | new InterfaceProxyBehavior(), 144 | new AnythingModifier() }); 145 | 146 | dynamic dynamically = alpha; 147 | Anything statically = alpha; 148 | IClayPlus interfacially = alpha; 149 | 150 | Assert.That(dynamically.Hello, Is.EqualTo("[World]")); 151 | Assert.That(statically.Hello, Is.EqualTo("[World]")); 152 | Assert.That(interfacially.Hello, Is.EqualTo("[World]")); 153 | 154 | Assert.That(dynamically.Add(3, 4), Is.EqualTo(9)); 155 | Assert.That(statically.Add(3, 4), Is.EqualTo(9)); 156 | Assert.That(interfacially.Add(3, 4), Is.EqualTo(9)); 157 | Assert.That(interfacially.Add(3, 5), Is.EqualTo(10)); 158 | Assert.That(interfacially.Add(3, 6), Is.EqualTo(11)); 159 | } 160 | 161 | class AnythingModifier : ClayBehavior 162 | { 163 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 164 | { 165 | if (name == "Add") 166 | { 167 | return (int)proceed() + 2; 168 | } 169 | return proceed(); 170 | } 171 | public override object GetMember(Func proceed, object self, string name) 172 | { 173 | if (name == "Hello") 174 | { 175 | return "[" + proceed() + "]"; 176 | } 177 | 178 | return proceed(); 179 | } 180 | 181 | } 182 | 183 | 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/ClaySharp.Tests/Implementation/NamedArgumentsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using ClaySharp.Implementation; 6 | using NUnit.Framework; 7 | 8 | namespace ClaySharp.Tests.Implementation 9 | { 10 | [TestFixture] 11 | public class NamedArgumentsTests 12 | { 13 | 14 | private INamedEnumerable AllNamed() 15 | { 16 | return Arguments.FromT(new[] { 1, 2, 3 }, new[] { "a", "b", "c" }); 17 | } 18 | 19 | private INamedEnumerable AllPositional() 20 | { 21 | return Arguments.FromT(new[] { 1, 2, 3 }, Enumerable.Empty()); 22 | } 23 | 24 | [Test] 25 | public void ZeroNamesGivesYouEntirelyPositionalArguments() 26 | { 27 | var args = AllPositional(); 28 | Assert.That(args.Count(), Is.EqualTo(3)); 29 | Assert.That(args.Positional.Count(), Is.EqualTo(3)); 30 | Assert.That(args.Named.Count(), Is.EqualTo(0)); 31 | } 32 | [Test] 33 | public void EqualNamesGivesYouEntirelyNamedArguments() 34 | { 35 | var args = AllNamed(); 36 | Assert.That(args.Count(), Is.EqualTo(3)); 37 | Assert.That(args.Positional.Count(), Is.EqualTo(0)); 38 | Assert.That(args.Named.Count(), Is.EqualTo(3)); 39 | } 40 | 41 | [Test] 42 | public void IteratingEmptyNamedAndPositionalCollections() 43 | { 44 | foreach (var named in AllPositional().Named) 45 | { 46 | Assert.Fail("Should not have named items"); 47 | } 48 | 49 | foreach (var positional in AllNamed().Positional) 50 | { 51 | Assert.Fail("Should not have positional items"); 52 | } 53 | } 54 | 55 | [Test] 56 | public void ContainsKeyWorksOnNames() 57 | { 58 | var args = AllNamed(); 59 | Assert.That(args.Named.ContainsKey("a"), Is.True); 60 | Assert.That(args.Named.ContainsKey("d"), Is.False); 61 | 62 | var args2 = AllPositional(); 63 | Assert.That(args2.Named.ContainsKey("a"), Is.False); 64 | Assert.That(args2.Named.ContainsKey("d"), Is.False); 65 | } 66 | 67 | [Test] 68 | public void ContainsWorksOnNameArgumentPairs() 69 | { 70 | var args = AllNamed(); 71 | Assert.That(args.Named.Contains(new KeyValuePair("a", 1)), Is.True); 72 | Assert.That(args.Named.Contains(new KeyValuePair("a", 2)), Is.False); 73 | Assert.That(args.Named.Contains(new KeyValuePair("d", 0)), Is.False); 74 | } 75 | 76 | 77 | [Test] 78 | public void NamedKeysCollectionIsAvailable() 79 | { 80 | var args = AllNamed(); 81 | Assert.That(args.Named.Keys.Count(), Is.EqualTo(3)); 82 | Assert.That(args.Named.Keys.Aggregate(">", (a, b) => a + b), Is.EqualTo(">abc")); 83 | 84 | var args2 = AllPositional(); 85 | Assert.That(args2.Named.Keys.Count(), Is.EqualTo(0)); 86 | Assert.That(args2.Named.Keys.Aggregate(">", (a, b) => a + b), Is.EqualTo(">")); 87 | } 88 | 89 | [Test] 90 | public void NamedValueCollectionIsAvailable() 91 | { 92 | var args = AllNamed(); 93 | Assert.That(args.Named.Values.Count(), Is.EqualTo(3)); 94 | Assert.That(args.Named.Values.Aggregate(">", (a, b) => a + b), Is.EqualTo(">123")); 95 | 96 | var args2 = AllPositional(); 97 | Assert.That(args2.Named.Values.Count(), Is.EqualTo(0)); 98 | Assert.That(args2.Named.Values.Aggregate(">", (a, b) => a + b), Is.EqualTo(">")); 99 | } 100 | 101 | [Test] 102 | public void NamedCollectionIsAvailable() 103 | { 104 | var args = AllNamed(); 105 | Assert.That(args.Named.Count(), Is.EqualTo(3)); 106 | Assert.That(args.Named.Aggregate(">", (a, b) => a + b.Key + b.Value), Is.EqualTo(">a1b2c3")); 107 | 108 | var args2 = AllPositional(); 109 | Assert.That(args2.Named.Count(), Is.EqualTo(0)); 110 | Assert.That(args2.Named.Aggregate(">", (a, b) => a + b.Key + b.Value), Is.EqualTo(">")); 111 | } 112 | 113 | [Test] 114 | public void NameIndexerWorksAsExpectedWithDefaultValueOnKeyMiss() 115 | { 116 | var args = AllNamed(); 117 | Assert.That(args.Named["a"], Is.EqualTo(1)); 118 | Assert.That(args.Named["b"], Is.EqualTo(2)); 119 | Assert.That(args.Named["c"], Is.EqualTo(3)); 120 | Assert.That(args.Named["d"], Is.EqualTo(default(int))); 121 | 122 | int vala; 123 | var hasa = args.Named.TryGetValue("a", out vala); 124 | Assert.That(vala, Is.EqualTo(1)); 125 | Assert.That(hasa, Is.True); 126 | 127 | int vald; 128 | var hasd = args.Named.TryGetValue("d", out vald); 129 | Assert.That(vald, Is.EqualTo(default(int))); 130 | Assert.That(hasd, Is.False); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/ClaySharp.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("DynShape.Tests")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Microsoft")] 11 | [assembly: AssemblyProduct("DynShape.Tests")] 12 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")] 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("df8430b1-a335-47af-beab-dccbf66da653")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/ClaySharp.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/ClaySharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClaySharp", "ClaySharp\ClaySharp.csproj", "{76BCD43B-7BA5-4B63-B1E1-861641CA2686}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClaySharp.Tests", "ClaySharp.Tests\ClaySharp.Tests.csproj", "{10369238-A590-48BF-8D3E-E83EB6F0C931}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {76BCD43B-7BA5-4B63-B1E1-861641CA2686}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {76BCD43B-7BA5-4B63-B1E1-861641CA2686}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {76BCD43B-7BA5-4B63-B1E1-861641CA2686}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {76BCD43B-7BA5-4B63-B1E1-861641CA2686}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {10369238-A590-48BF-8D3E-E83EB6F0C931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {10369238-A590-48BF-8D3E-E83EB6F0C931}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {10369238-A590-48BF-8D3E-E83EB6F0C931}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {10369238-A590-48BF-8D3E-E83EB6F0C931}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /src/ClaySharp/Behaviors/ArrayBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace ClaySharp.Behaviors 7 | { 8 | public class ArrayBehavior : ClayBehavior 9 | { 10 | readonly List _data = new List(); 11 | 12 | public override object GetIndex(Func proceed, object self, IEnumerable keys) 13 | { 14 | return IfSingleInteger(keys, key => _data[key], proceed); 15 | } 16 | 17 | public override object SetIndex(Func proceed, object self, IEnumerable keys, object value) 18 | { 19 | return IfSingleInteger(keys, key => _data[key] = value, proceed); 20 | } 21 | 22 | public override object GetMember(Func proceed, object self, string name) 23 | { 24 | switch (name) 25 | { 26 | case "Length": 27 | case "Count": 28 | return _data.Count; 29 | case "GetEnumerator": 30 | return new Clay(new InterfaceProxyBehavior(), new EnumeratorBehavior(_data.GetEnumerator())); 31 | } 32 | return proceed(); 33 | } 34 | 35 | 36 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 37 | { 38 | switch (name) 39 | { 40 | case "AddRange": 41 | _data.AddRange(((IEnumerable)args.Single()).OfType()); 42 | return self; 43 | case "Add": 44 | _data.AddRange(args); 45 | return self; 46 | case "Insert": 47 | return IfInitialInteger(args, (index, arr) => { _data.InsertRange(index, arr); return self; }, proceed); 48 | case "RemoveAt": 49 | return IfSingleInteger(args, index => { _data.RemoveAt(index); return self; }, proceed); 50 | case "Contains": 51 | return IfSingleArgument(args, arg => _data.Contains(arg), proceed); 52 | case "IndexOf": 53 | return IfSingleArgument(args, arg => _data.IndexOf(arg), proceed); 54 | case "Remove": 55 | return IfSingleArgument(args, arg => _data.Remove(arg), proceed); 56 | case "CopyTo": 57 | return IfArguments(args, (array, arrayIndex) => 58 | { 59 | _data.CopyTo(array, arrayIndex); 60 | return self; 61 | }, proceed); 62 | 63 | } 64 | 65 | if (!args.Any()) 66 | { 67 | return GetMember(proceed, self, name); 68 | } 69 | 70 | return proceed(); 71 | } 72 | 73 | 74 | 75 | private static object IfArguments(IEnumerable args, Func func, Func proceed) 76 | { 77 | if (args.Count() != 2) 78 | return proceed(); 79 | return func((T1)args.First(), (T2)args.Last()); 80 | } 81 | 82 | 83 | private static object IfSingleArgument(IEnumerable args, Func func, Func proceed) 84 | { 85 | return args.Count() == 1 ? func(args.Single()) : proceed(); 86 | } 87 | 88 | private static object IfSingleInteger(IEnumerable args, Func func, Func proceed) 89 | { 90 | if (args.Count() != 1) 91 | return proceed(); 92 | 93 | return IfInitialInteger(args, (index, ignored) => func(index), proceed); 94 | } 95 | 96 | private static object IfInitialInteger(IEnumerable args, Func, object> func, Func proceed) 97 | { 98 | if (!args.Any()) 99 | return proceed(); 100 | 101 | var key = args.First(); 102 | 103 | if (key.GetType() != typeof(int)) 104 | return proceed(); 105 | 106 | return func((int)key, args.Skip(1)); 107 | } 108 | 109 | 110 | 111 | // small, dynamic wrapper around underlying IEnumerator. use of 112 | // dlr and dynamic proxy enables this enumerator to be used in places 113 | // where the array is being cast into a strong collection interface 114 | class EnumeratorBehavior : ClayBehavior 115 | { 116 | private readonly IEnumerator _enumerator; 117 | 118 | public EnumeratorBehavior(IEnumerator enumerator) 119 | { 120 | _enumerator = enumerator; 121 | } 122 | 123 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 124 | { 125 | switch (name) 126 | { 127 | case "MoveNext": 128 | return _enumerator.MoveNext(); 129 | case "Reset": 130 | _enumerator.Reset(); 131 | return null; 132 | case "Dispose": 133 | if (_enumerator is IDisposable) 134 | ((IDisposable)_enumerator).Dispose(); 135 | return null; 136 | } 137 | return proceed(); 138 | } 139 | 140 | public override object GetMember(Func proceed, object self, string name) 141 | { 142 | switch (name) 143 | { 144 | case "Current": 145 | return _enumerator.Current; 146 | } 147 | return proceed(); 148 | } 149 | } 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/ClaySharp/Behaviors/ArrayFactoryBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace ClaySharp.Behaviors 7 | { 8 | public class ArrayFactoryBehavior : ClayBehavior 9 | { 10 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 11 | { 12 | if (name == "Array") 13 | { 14 | dynamic x = new Clay( 15 | new InterfaceProxyBehavior(), 16 | new PropBehavior(), 17 | new ArrayPropAssignmentBehavior(), 18 | new ArrayBehavior(), 19 | new NilResultBehavior()); 20 | x.AddRange(args); 21 | return x; 22 | } 23 | return proceed(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ClaySharp/Behaviors/ArrayPropAssignmentBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace ClaySharp.Behaviors 6 | { 7 | public class ArrayPropAssignmentBehavior : ClayBehavior 8 | { 9 | 10 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 11 | { 12 | return 13 | IfSingleArray(args, arr => { ((dynamic)self)[name] = arr; return self; }, () => 14 | IfTwoOrMoreArgs(args, arr => { ((dynamic)self)[name] = arr; return self; }, 15 | proceed)); 16 | } 17 | 18 | private object IfTwoOrMoreArgs(IEnumerable args, Func func, Func proceed) 19 | { 20 | if (args.Count() < 2) 21 | return proceed(); 22 | 23 | return func(NewArray().AddRange(args)); 24 | } 25 | 26 | private object IfSingleArray(IEnumerable args, Func func, Func proceed) 27 | { 28 | if (args.Count() != 1) 29 | return proceed(); 30 | 31 | var arr = args.Single(); 32 | if (arr == null) 33 | return proceed(); 34 | 35 | if (!typeof(Array).IsAssignableFrom(arr.GetType())) 36 | return proceed(); 37 | 38 | return func(NewArray().AddRange(arr)); 39 | } 40 | 41 | private static dynamic NewArray() 42 | { 43 | return new Clay( 44 | new InterfaceProxyBehavior(), 45 | new PropBehavior(), 46 | new ArrayPropAssignmentBehavior(), 47 | new ArrayBehavior(), 48 | new NilResultBehavior()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ClaySharp/Behaviors/ClayFactoryBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Microsoft.CSharp.RuntimeBinder; 6 | 7 | namespace ClaySharp.Behaviors 8 | { 9 | public class ClayFactoryBehavior : ClayBehavior 10 | { 11 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 12 | { 13 | 14 | dynamic shape = new Clay( 15 | new InterfaceProxyBehavior(), 16 | new PropBehavior(), 17 | new ArrayPropAssignmentBehavior(), 18 | new NilResultBehavior()); 19 | 20 | shape.ShapeName = name; 21 | 22 | if (args.Positional.Count() == 1) 23 | { 24 | var options = args.Positional.Single(); 25 | var assigner = GetAssigner(options.GetType()); 26 | assigner.Invoke(shape, options); 27 | } 28 | 29 | foreach (var kv in args.Named) 30 | { 31 | shape[kv.Key] = kv.Value; 32 | } 33 | 34 | return shape; 35 | } 36 | 37 | 38 | 39 | private static Action GetAssigner(Type sourceType) 40 | { 41 | lock (_assignerCache) 42 | { 43 | Action assigner; 44 | if (_assignerCache.TryGetValue(sourceType, out assigner)) 45 | return assigner; 46 | 47 | 48 | // given "sourceType T" with public properties, e.g. X and Y, generate the following lambda 49 | // 50 | // (dynamic target, object source) => { 51 | // target.X = ((T)source).X; 52 | // target.Y = ((T)source).Y; 53 | // } 54 | 55 | var targetParameter = Expression.Parameter(typeof(object), "target"); 56 | var sourceParameter = Expression.Parameter(typeof(object), "source"); 57 | 58 | // for each propertyInfo, e.g. X 59 | // produce dynamic call site, (target).X = ((T)source).X 60 | var assignments = sourceType.GetProperties().Select( 61 | property => Expression.Dynamic( 62 | Binder.SetMember( 63 | CSharpBinderFlags.None, 64 | property.Name, 65 | typeof(void), 66 | new[] { 67 | CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 68 | CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) 69 | }), 70 | typeof(void), 71 | targetParameter, 72 | Expression.Property( 73 | Expression.Convert(sourceParameter, sourceType), 74 | property))); 75 | 76 | 77 | var lambda = Expression.Lambda>( 78 | Expression.Block(assignments), 79 | targetParameter, 80 | sourceParameter); 81 | 82 | assigner = lambda.Compile(); 83 | _assignerCache.Add(sourceType, assigner); 84 | return assigner; 85 | } 86 | 87 | } 88 | 89 | 90 | static readonly Dictionary> _assignerCache = new Dictionary>(); 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/ClaySharp/Behaviors/InterfaceProxyBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Dynamic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using Castle.DynamicProxy; 9 | using Microsoft.CSharp.RuntimeBinder; 10 | using Binder = Microsoft.CSharp.RuntimeBinder.Binder; 11 | 12 | namespace ClaySharp.Behaviors 13 | { 14 | public class InterfaceProxyBehavior : ClayBehavior 15 | { 16 | private static readonly IProxyBuilder ProxyBuilder = new DefaultProxyBuilder(); 17 | static readonly MethodInfo DynamicMetaObjectProviderGetMetaObject = typeof(IDynamicMetaObjectProvider).GetMethod("GetMetaObject"); 18 | 19 | public override object ConvertMissing(Func proceed, object self, Type type, bool isExplicit) 20 | { 21 | if (type.IsInterface && type != typeof(IDynamicMetaObjectProvider)) 22 | { 23 | var proxyType = ProxyBuilder.CreateInterfaceProxyTypeWithoutTarget( 24 | type, 25 | new[] { typeof(IDynamicMetaObjectProvider) }, 26 | ProxyGenerationOptions.Default); 27 | 28 | var interceptors = new IInterceptor[] { new Interceptor(self) }; 29 | var proxy = Activator.CreateInstance(proxyType, new object[] { interceptors, self }); 30 | return proxy; 31 | } 32 | 33 | return proceed(); 34 | } 35 | 36 | class Interceptor : IInterceptor 37 | { 38 | public object Self { get; private set; } 39 | 40 | public Interceptor(object self) 41 | { 42 | Self = self; 43 | } 44 | 45 | public void Intercept(IInvocation invocation) 46 | { 47 | if (invocation.Method == DynamicMetaObjectProviderGetMetaObject) 48 | { 49 | var expression = (Expression)invocation.Arguments.Single(); 50 | invocation.ReturnValue = new ForwardingMetaObject( 51 | expression, 52 | BindingRestrictions.Empty, 53 | invocation.Proxy, 54 | (IDynamicMetaObjectProvider)Self, 55 | exprProxy => Expression.Field(exprProxy, "__target")); 56 | 57 | return; 58 | } 59 | 60 | var invoker = BindInvoker(invocation); 61 | invoker(invocation); 62 | 63 | if (invocation.ReturnValue != null && 64 | !invocation.Method.ReturnType.IsAssignableFrom(invocation.ReturnValue.GetType()) && 65 | invocation.ReturnValue is IClayBehaviorProvider) 66 | { 67 | 68 | var returnValueBehavior = ((IClayBehaviorProvider)invocation.ReturnValue).Behavior; 69 | invocation.ReturnValue = returnValueBehavior.Convert( 70 | () => returnValueBehavior.ConvertMissing( 71 | () => invocation.ReturnValue, 72 | invocation.ReturnValue, 73 | invocation.Method.ReturnType, 74 | false), 75 | invocation.ReturnValue, 76 | invocation.Method.ReturnType, 77 | false); 78 | } 79 | } 80 | 81 | 82 | static readonly ConcurrentDictionary> Invokers = new ConcurrentDictionary>(); 83 | 84 | private static Action BindInvoker(IInvocation invocation) 85 | { 86 | return Invokers.GetOrAdd(invocation.Method, CompileInvoker); 87 | } 88 | 89 | private static Action CompileInvoker(MethodInfo method) 90 | { 91 | 92 | var methodParameters = method.GetParameters(); 93 | var invocationParameter = Expression.Parameter(typeof(IInvocation), "invocation"); 94 | 95 | var targetAndArgumentInfos = Pack( 96 | CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 97 | methodParameters.Select( 98 | mp => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, mp.Name))); 99 | 100 | var targetAndArguments = Pack( 101 | Expression.Property(invocationParameter, invocationParameter.Type, "Proxy"), 102 | methodParameters.Select( 103 | (mp, index) => 104 | Expression.Convert( 105 | Expression.ArrayIndex( 106 | Expression.Property(invocationParameter, invocationParameter.Type, 107 | "Arguments"), 108 | Expression.Constant(index)), mp.ParameterType))); 109 | 110 | Expression body = null; 111 | if (method.IsSpecialName) 112 | { 113 | if (body == null && method.Name.Equals("get_Item")) 114 | { 115 | body = Expression.Dynamic( 116 | Binder.GetIndex( 117 | CSharpBinderFlags.InvokeSpecialName, 118 | typeof(object), 119 | targetAndArgumentInfos), 120 | typeof(object), 121 | targetAndArguments); 122 | } 123 | 124 | if (body == null && method.Name.Equals("set_Item")) 125 | { 126 | 127 | var targetAndArgumentInfosWithoutTheNameValue = Pack( 128 | CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 129 | methodParameters.Select( 130 | mp => mp.Name == "value" ? CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) : CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, mp.Name))); 131 | 132 | body = Expression.Dynamic( 133 | Binder.SetIndex( 134 | CSharpBinderFlags.InvokeSpecialName, 135 | typeof(object), 136 | targetAndArgumentInfosWithoutTheNameValue), 137 | typeof(object), 138 | targetAndArguments); 139 | } 140 | 141 | if (body == null && method.Name.StartsWith("get_")) 142 | { 143 | // Build lambda containing the following call site: 144 | // (IInvocation invocation) => { 145 | // invocation.ReturnValue = (object) ((dynamic)invocation.InvocationTarget).{method.Name}; 146 | // } 147 | body = Expression.Dynamic( 148 | Binder.GetMember( 149 | CSharpBinderFlags.InvokeSpecialName, 150 | method.Name.Substring("get_".Length), 151 | typeof(object), 152 | targetAndArgumentInfos), 153 | typeof(object), 154 | targetAndArguments); 155 | } 156 | 157 | if (body == null && method.Name.StartsWith("set_")) 158 | { 159 | body = Expression.Dynamic( 160 | Binder.SetMember( 161 | CSharpBinderFlags.InvokeSpecialName, 162 | method.Name.Substring("set_".Length), 163 | typeof(object), 164 | targetAndArgumentInfos), 165 | typeof(object), 166 | targetAndArguments); 167 | } 168 | } 169 | if (body == null) 170 | { 171 | // Build lambda containing the following call site: 172 | // (IInvocation invocation) => { 173 | // invocation.ReturnValue = (object) ((dynamic)invocation.InvocationTarget).{method.Name}( 174 | // {methodParameters[*].Name}: ({methodParameters[*].Type})invocation.Arguments[*], 175 | // ...); 176 | // } 177 | 178 | 179 | body = Expression.Dynamic( 180 | Binder.InvokeMember( 181 | CSharpBinderFlags.None, 182 | method.Name, 183 | null, 184 | typeof(object), 185 | targetAndArgumentInfos), 186 | typeof(object), 187 | targetAndArguments); 188 | } 189 | 190 | if (body != null && method.ReturnType != typeof(void)) 191 | { 192 | body = Expression.Assign( 193 | Expression.Property(invocationParameter, invocationParameter.Type, "ReturnValue"), 194 | Expression.Convert(body, typeof(object))); 195 | } 196 | 197 | var lambda = Expression.Lambda>(body, invocationParameter); 198 | return lambda.Compile(); 199 | } 200 | 201 | } 202 | 203 | static IEnumerable Pack(T t1) 204 | { 205 | if (!Equals(t1, default(T))) 206 | yield return t1; 207 | } 208 | static IEnumerable Pack(T t1, IEnumerable t2) 209 | { 210 | if (!Equals(t1, default(T))) 211 | yield return t1; 212 | foreach (var t in t2) 213 | yield return t; 214 | } 215 | static IEnumerable Pack(T t1, IEnumerable t2, T t3) 216 | { 217 | if (!Equals(t1, default(T))) 218 | yield return t1; 219 | foreach (var t in t2) 220 | yield return t; 221 | if (!Equals(t3, default(T))) 222 | yield return t3; 223 | } 224 | 225 | /// 226 | /// Based on techniques discussed by Tomáš Matoušek 227 | /// at http://blog.tomasm.net/2009/11/07/forwarding-meta-object/ 228 | /// 229 | public sealed class ForwardingMetaObject : DynamicMetaObject 230 | { 231 | private readonly DynamicMetaObject _metaForwardee; 232 | 233 | public ForwardingMetaObject(Expression expression, BindingRestrictions restrictions, object forwarder, 234 | IDynamicMetaObjectProvider forwardee, Func forwardeeGetter) 235 | : base(expression, restrictions, forwarder) 236 | { 237 | 238 | // We'll use forwardee's meta-object to bind dynamic operations. 239 | _metaForwardee = forwardee.GetMetaObject( 240 | forwardeeGetter( 241 | Expression.Convert(expression, forwarder.GetType()) // [1] 242 | ) 243 | ); 244 | } 245 | 246 | // Restricts the target object's type to TForwarder. 247 | // The meta-object we are forwarding to assumes that it gets an instance of TForwarder (see [1]). 248 | // We need to ensure that the assumption holds. 249 | private DynamicMetaObject AddRestrictions(DynamicMetaObject result) 250 | { 251 | var restricted = new DynamicMetaObject( 252 | result.Expression, 253 | BindingRestrictions.GetTypeRestriction(Expression, Value.GetType()).Merge(result.Restrictions), 254 | _metaForwardee.Value 255 | ); 256 | return restricted; 257 | } 258 | 259 | // Forward all dynamic operations or some of them as needed // 260 | 261 | public override DynamicMetaObject BindGetMember(GetMemberBinder binder) 262 | { 263 | return AddRestrictions(_metaForwardee.BindGetMember(binder)); 264 | } 265 | 266 | public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) 267 | { 268 | return AddRestrictions(_metaForwardee.BindSetMember(binder, value)); 269 | } 270 | 271 | public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) 272 | { 273 | return AddRestrictions(_metaForwardee.BindDeleteMember(binder)); 274 | } 275 | 276 | public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) 277 | { 278 | return AddRestrictions(_metaForwardee.BindGetIndex(binder, indexes)); 279 | } 280 | 281 | public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) 282 | { 283 | return AddRestrictions(_metaForwardee.BindSetIndex(binder, indexes, value)); 284 | } 285 | 286 | public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) 287 | { 288 | return AddRestrictions(_metaForwardee.BindDeleteIndex(binder, indexes)); 289 | } 290 | 291 | public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) 292 | { 293 | return AddRestrictions(_metaForwardee.BindInvokeMember(binder, args)); 294 | } 295 | 296 | public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) 297 | { 298 | return AddRestrictions(_metaForwardee.BindInvoke(binder, args)); 299 | } 300 | 301 | public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) 302 | { 303 | return AddRestrictions(_metaForwardee.BindCreateInstance(binder, args)); 304 | } 305 | 306 | public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) 307 | { 308 | return AddRestrictions(_metaForwardee.BindUnaryOperation(binder)); 309 | } 310 | 311 | public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) 312 | { 313 | return AddRestrictions(_metaForwardee.BindBinaryOperation(binder, arg)); 314 | } 315 | 316 | public override DynamicMetaObject BindConvert(ConvertBinder binder) 317 | { 318 | return AddRestrictions(_metaForwardee.BindConvert(binder)); 319 | } 320 | 321 | 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/ClaySharp/Behaviors/NilBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace ClaySharp.Behaviors 7 | { 8 | public class NilBehavior : ClayBehavior 9 | { 10 | 11 | public override object GetMember(Func proceed, object self, string name) 12 | { 13 | return Nil.Instance; 14 | } 15 | 16 | public override object GetIndex(Func proceed, object self, IEnumerable keys) 17 | { 18 | return Nil.Instance; 19 | } 20 | 21 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 22 | { 23 | if (args.Any()) 24 | return proceed(); 25 | 26 | if (name == "ToString") 27 | { 28 | return string.Empty; 29 | } 30 | 31 | return Nil.Instance; 32 | } 33 | 34 | public override object Convert(Func proceed, object self, Type type, bool isExplicit) 35 | { 36 | if (type.IsInterface) 37 | return proceed(); 38 | 39 | return null; 40 | } 41 | 42 | public override object BinaryOperation(Func proceed, object self, ExpressionType operation, object value) 43 | { 44 | switch (operation) 45 | { 46 | case ExpressionType.Equal: 47 | return ReferenceEquals(value, Nil.Instance) || value == null; 48 | case ExpressionType.NotEqual: 49 | return !ReferenceEquals(value, Nil.Instance) && value != null; 50 | } 51 | 52 | return proceed(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/ClaySharp/Behaviors/NilResultBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace ClaySharp.Behaviors 6 | { 7 | public class NilResultBehavior : ClayBehavior 8 | { 9 | 10 | public override object GetMember(Func proceed, object self, string name) 11 | { 12 | return proceed() ?? Nil.Instance; 13 | } 14 | 15 | public override object GetIndex(Func proceed, object self, IEnumerable keys) 16 | { 17 | return proceed() ?? Nil.Instance; 18 | } 19 | 20 | public override object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 21 | { 22 | if (args.Any()) 23 | return proceed(); 24 | 25 | return proceed() ?? Nil.Instance; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/ClaySharp/Behaviors/PropBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace ClaySharp.Behaviors 6 | { 7 | public class PropBehavior : ClayBehavior 8 | { 9 | readonly Dictionary _props = new Dictionary(); 10 | 11 | public override object GetMembers(System.Func proceed, object self, IDictionary members) 12 | { 13 | foreach (var pair in _props) 14 | { 15 | members.Add(pair.Key.ToString(), pair.Value); 16 | } 17 | 18 | return proceed(); 19 | } 20 | 21 | public override object GetMemberMissing(Func proceed, object self, string name) 22 | { 23 | object value; 24 | return _props.TryGetValue(name, out value) ? value : null; 25 | } 26 | 27 | public override object SetMemberMissing(Func proceed, object self, string name, object value) 28 | { 29 | return _props[name] = value; 30 | } 31 | 32 | public override object InvokeMemberMissing(Func proceed, object self, string name, INamedEnumerable args) 33 | { 34 | if (!args.Any()) 35 | { 36 | return GetMemberMissing(proceed, self, name); 37 | } 38 | 39 | if (args.Count() == 1) 40 | { 41 | SetMemberMissing(proceed, self, name, args.Single()); 42 | return self; 43 | } 44 | 45 | return proceed(); 46 | } 47 | 48 | public override object GetIndex(Func proceed, object self, IEnumerable keys) 49 | { 50 | if (keys.Count() != 1) proceed(); 51 | 52 | object value; 53 | return _props.TryGetValue(keys.Single(), out value) ? value : null; 54 | } 55 | 56 | public override object SetIndex(Func proceed, object self, IEnumerable keys, object value) 57 | { 58 | if (keys.Count() != 1) proceed(); 59 | 60 | return _props[keys.Single()] = value; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/ClaySharp/Clay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Dynamic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using ClaySharp.Implementation; 7 | 8 | namespace ClaySharp 9 | { 10 | 11 | public class Clay : IDynamicMetaObjectProvider, IClayBehaviorProvider 12 | { 13 | private readonly ClayBehaviorCollection _behavior; 14 | 15 | public Clay() 16 | : this(Enumerable.Empty()) 17 | { 18 | } 19 | 20 | public Clay(params IClayBehavior[] behaviors) 21 | : this(behaviors.AsEnumerable()) 22 | { 23 | } 24 | 25 | public Clay(IEnumerable behaviors) 26 | { 27 | _behavior = new ClayBehaviorCollection(behaviors); 28 | } 29 | 30 | DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) 31 | { 32 | return new ClayMetaObject(this, parameter); 33 | } 34 | 35 | IClayBehavior IClayBehaviorProvider.Behavior 36 | { 37 | get { return _behavior; } 38 | } 39 | 40 | public override string ToString() 41 | { 42 | var fallback = base.ToString(); 43 | return _behavior.InvokeMember(() => fallback, this, "ToString", Arguments.Empty()) as string; 44 | } 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ClaySharp/ClayActivator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ClaySharp 5 | { 6 | public static class ClayActivator 7 | { 8 | static ClayActivator() 9 | { 10 | var instance = new DefaultClayActivator(); 11 | ServiceLocator = () => instance; 12 | } 13 | 14 | public static Func ServiceLocator { get; set; } 15 | 16 | public static dynamic CreateInstance(IEnumerable behaviors, params object[] arguments) 17 | { 18 | return ServiceLocator().CreateInstance(typeof(Clay), behaviors, arguments); 19 | } 20 | 21 | public static dynamic CreateInstance(Type baseType, IEnumerable behaviors, params object[] arguments) 22 | { 23 | return ServiceLocator().CreateInstance(baseType, behaviors, arguments); 24 | } 25 | 26 | public static dynamic CreateInstance(IEnumerable behaviors, params object[] arguments) 27 | { 28 | return ServiceLocator().CreateInstance(typeof(TBase), behaviors, arguments); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ClaySharp/ClayBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace ClaySharp 6 | { 7 | public abstract class ClayBehavior : IClayBehavior 8 | { 9 | public virtual object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) { return proceed(); } 10 | public virtual object GetMember(Func proceed, object self, string name) { return proceed(); } 11 | public virtual object SetMember(Func proceed, object self, string name, object value) { return proceed(); } 12 | public virtual object GetIndex(Func proceed, object self, IEnumerable keys) { return proceed(); } 13 | public virtual object SetIndex(Func proceed, object self, IEnumerable keys, object value) { return proceed(); } 14 | public virtual object Convert(Func proceed, object self, Type type, bool isExplicit) { return proceed(); } 15 | public virtual object BinaryOperation(Func proceed, object self, ExpressionType operation, object value) { return proceed(); } 16 | public virtual object GetMembers(Func proceed, object self, IDictionary members) { return proceed(); } 17 | 18 | public virtual object InvokeMemberMissing(Func proceed, object self, string name, INamedEnumerable args) { return proceed(); } 19 | public virtual object GetMemberMissing(Func proceed, object self, string name) { return proceed(); } 20 | public virtual object SetMemberMissing(Func proceed, object self, string name, object value) { return proceed(); } 21 | public virtual object ConvertMissing(Func proceed, object self, Type type, bool isExplicit) { return proceed(); } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ClaySharp/ClayBehaviorCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace ClaySharp 7 | { 8 | public class ClayBehaviorCollection : List, IClayBehavior 9 | { 10 | public ClayBehaviorCollection(IEnumerable behaviors) 11 | : base(behaviors) 12 | { 13 | } 14 | 15 | object Execute(Func proceed, Func, IClayBehavior, Func> linker) 16 | { 17 | return this.Aggregate(proceed, linker)(); 18 | } 19 | 20 | public object GetMember(Func proceed, object self, string name) 21 | { 22 | return Execute(proceed, (next, behavior) => () => behavior.GetMember(next, self, name)); 23 | } 24 | 25 | public object SetMember(Func proceed, object self, string name, object value) 26 | { 27 | return Execute(proceed, (next, behavior) => () => behavior.SetMember(next, self, name, value)); 28 | } 29 | 30 | public object GetMembers(Func proceed, object self, IDictionary members) 31 | { 32 | return Execute(proceed, (next, behavior) => () => behavior.GetMembers(next, self, members)); 33 | } 34 | 35 | public object InvokeMember(Func proceed, object self, string name, INamedEnumerable args) 36 | { 37 | return Execute(proceed, (next, behavior) => () => behavior.InvokeMember(next, self, name, args)); 38 | } 39 | 40 | public object GetIndex(Func proceed, object self, IEnumerable keys) 41 | { 42 | return Execute(proceed, (next, behavior) => () => behavior.GetIndex(next, self, keys)); 43 | } 44 | 45 | public object SetIndex(Func proceed, object self, IEnumerable keys, object value) 46 | { 47 | return Execute(proceed, (next, behavior) => () => behavior.SetIndex(next, self, keys, value)); 48 | } 49 | 50 | public object Convert(Func proceed, object self, Type type, bool isExplicit) 51 | { 52 | return Execute(proceed, (next, behavior) => () => behavior.Convert(next, self, type, isExplicit)); 53 | } 54 | 55 | public object BinaryOperation(Func proceed, object self, ExpressionType operation, object value) 56 | { 57 | return Execute(proceed, (next, behavior) => () => behavior.BinaryOperation(next, self, operation, value)); 58 | } 59 | 60 | public object InvokeMemberMissing(Func proceed, object self, string name, INamedEnumerable args) 61 | { 62 | return Execute(proceed, (next, behavior) => () => behavior.InvokeMemberMissing(next, self, name, args)); 63 | } 64 | 65 | public object GetMemberMissing(Func proceed, object self, string name) 66 | { 67 | return Execute(proceed, (next, behavior) => () => behavior.GetMemberMissing(next, self, name)); 68 | } 69 | 70 | public object SetMemberMissing(Func proceed, object self, string name, object value) 71 | { 72 | return Execute(proceed, (next, behavior) => () => behavior.SetMemberMissing(next, self, name, value)); 73 | } 74 | 75 | public object ConvertMissing(Func proceed, object self, Type type, bool isExplicit) 76 | { 77 | return Execute(proceed, (next, behavior) => () => behavior.ConvertMissing(next, self, type, isExplicit)); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/ClaySharp/ClayFactory.cs: -------------------------------------------------------------------------------- 1 | using ClaySharp.Behaviors; 2 | 3 | namespace ClaySharp 4 | { 5 | public class ClayFactory : Clay 6 | { 7 | public ClayFactory() : base(new ClayFactoryBehavior(), new ArrayFactoryBehavior()) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ClaySharp/ClayInteceptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using Microsoft.CSharp.RuntimeBinder; 6 | using Castle.DynamicProxy; 7 | using ClaySharp.Implementation; 8 | 9 | namespace ClaySharp 10 | { 11 | public class ClayInterceptor : IInterceptor 12 | { 13 | private const string GetPrefix = "get_"; 14 | private const string SetPrefix = "set_"; 15 | 16 | public void Intercept(IInvocation invocation) 17 | { 18 | var invocationDestinedForSelf = ReferenceEquals(invocation.InvocationTarget, invocation.Proxy); 19 | if (!invocationDestinedForSelf) 20 | { 21 | // don't intercept mixins 22 | invocation.Proceed(); 23 | return; 24 | } 25 | 26 | var behaviorProvider = invocation.Proxy as IClayBehaviorProvider; 27 | if (behaviorProvider != null) 28 | { 29 | var invocationMethod = invocation.Method; 30 | if (invocationMethod.IsSpecialName && 31 | invocationMethod.Name.StartsWith(GetPrefix)) 32 | { 33 | invocation.ReturnValue = behaviorProvider.Behavior.GetMember( 34 | () => 35 | { 36 | invocation.Proceed(); 37 | return invocation.ReturnValue; 38 | }, 39 | invocation.Proxy, 40 | invocationMethod.Name.Substring(GetPrefix.Length)); 41 | AdjustReturnValue(invocation); 42 | return; 43 | } 44 | if (invocationMethod.IsSpecialName && 45 | invocationMethod.Name.StartsWith(SetPrefix) && 46 | invocation.Arguments.Count() == 1) 47 | { 48 | invocation.ReturnValue = behaviorProvider.Behavior.SetMember( 49 | () => 50 | { 51 | invocation.Proceed(); 52 | return invocation.ReturnValue; 53 | }, 54 | invocation.Proxy, 55 | invocationMethod.Name.Substring(SetPrefix.Length), 56 | invocation.Arguments.Single()); 57 | AdjustReturnValue(invocation); 58 | return; 59 | } 60 | 61 | if (!invocationMethod.IsSpecialName) 62 | { 63 | invocation.ReturnValue = behaviorProvider.Behavior.InvokeMember( 64 | () => { invocation.Proceed(); return invocation.ReturnValue; }, 65 | invocation.Proxy, 66 | invocationMethod.Name, 67 | Arguments.From(invocation.Arguments, Enumerable.Empty())); 68 | AdjustReturnValue(invocation); 69 | return; 70 | } 71 | } 72 | invocation.Proceed(); 73 | } 74 | 75 | static readonly ConcurrentDictionary>> _convertSites = new ConcurrentDictionary>>(); 76 | 77 | private static void AdjustReturnValue(IInvocation invocation) 78 | { 79 | var methodReturnType = invocation.Method.ReturnType; 80 | if (methodReturnType == typeof(void)) 81 | return; 82 | 83 | if (invocation.ReturnValue == null) 84 | return; 85 | 86 | var returnValueType = invocation.ReturnValue.GetType(); 87 | if (methodReturnType.IsAssignableFrom(returnValueType)) 88 | return; 89 | 90 | var callSite = _convertSites.GetOrAdd( 91 | methodReturnType, 92 | x => CallSite>.Create( 93 | Binder.Convert(CSharpBinderFlags.None, x, null))); 94 | 95 | invocation.ReturnValue = callSite.Target(callSite, invocation.ReturnValue); 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /src/ClaySharp/ClayMetaObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Dynamic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using ClaySharp.Implementation; 8 | 9 | namespace ClaySharp 10 | { 11 | public class ClayMetaObject : DynamicMetaObject 12 | { 13 | // ReSharper disable InconsistentNaming 14 | private static readonly MethodInfo IClayBehavior_InvokeMember = typeof(IClayBehavior).GetMethod("InvokeMember"); 15 | private static readonly MethodInfo IClayBehavior_GetMember = typeof(IClayBehavior).GetMethod("GetMember"); 16 | private static readonly MethodInfo IClayBehavior_SetMember = typeof(IClayBehavior).GetMethod("SetMember"); 17 | private static readonly MethodInfo IClayBehavior_GetIndex = typeof(IClayBehavior).GetMethod("GetIndex"); 18 | private static readonly MethodInfo IClayBehavior_SetIndex = typeof(IClayBehavior).GetMethod("SetIndex"); 19 | private static readonly MethodInfo IClayBehavior_BinaryOperation = typeof(IClayBehavior).GetMethod("BinaryOperation"); 20 | private static readonly MethodInfo IClayBehavior_Convert = typeof(IClayBehavior).GetMethod("Convert"); 21 | 22 | private static readonly MethodInfo IClayBehavior_InvokeMemberMissing = typeof(IClayBehavior).GetMethod("InvokeMemberMissing"); 23 | private static readonly MethodInfo IClayBehavior_GetMemberMissing = typeof(IClayBehavior).GetMethod("GetMemberMissing"); 24 | private static readonly MethodInfo IClayBehavior_SetMemberMissing = typeof(IClayBehavior).GetMethod("SetMemberMissing"); 25 | private static readonly MethodInfo IClayBehavior_ConvertMissing = typeof(IClayBehavior).GetMethod("ConvertMissing"); 26 | 27 | // ReSharper restore InconsistentNaming 28 | 29 | public ClayMetaObject(object value, Expression expression) 30 | : base(expression, BindingRestrictions.Empty, value) 31 | { 32 | Logger = NullLogger.Instance; 33 | } 34 | 35 | public ClayMetaObject(object value, Expression expression, Func getClayBehavior) 36 | : this(value, expression) 37 | { 38 | _getClayBehavior = getClayBehavior; 39 | } 40 | 41 | public ILogger Logger { get; set; } 42 | 43 | private Expression GetLimitedSelf() 44 | { 45 | if (Expression.Type == LimitType || Expression.Type.IsEquivalentTo(LimitType)) 46 | { 47 | return Expression; 48 | } 49 | return Expression.Convert(Expression, LimitType); 50 | } 51 | 52 | readonly Func _getClayBehavior = expr => Expression.Property( 53 | Expression.Convert(expr, typeof(IClayBehaviorProvider)), 54 | "Behavior"); 55 | 56 | protected virtual Expression GetClayBehavior() 57 | { 58 | return _getClayBehavior(Expression); 59 | } 60 | 61 | 62 | public override DynamicMetaObject BindGetMember(GetMemberBinder binder) 63 | { 64 | Logger.Log(LogLevel.Debug, null, "BindGetMember"); 65 | 66 | var binderDefault = binder.FallbackGetMember(this); 67 | 68 | 69 | var missingLambda = Expression.Lambda(Expression.Call( 70 | GetClayBehavior(), 71 | IClayBehavior_GetMemberMissing, 72 | Expression.Lambda(binderDefault.Expression), 73 | GetLimitedSelf(), 74 | Expression.Constant(binder.Name, typeof(string)))); 75 | 76 | var call = Expression.Call( 77 | GetClayBehavior(), 78 | IClayBehavior_GetMember, 79 | missingLambda, 80 | GetLimitedSelf(), 81 | Expression.Constant(binder.Name, typeof(string))); 82 | 83 | var dynamicSuggestion = new DynamicMetaObject(call, BindingRestrictions.GetTypeRestriction(Expression, LimitType).Merge(binderDefault.Restrictions)); 84 | 85 | return binder.FallbackGetMember(this, dynamicSuggestion); 86 | } 87 | 88 | public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) 89 | { 90 | Logger.Log(LogLevel.Debug, null, "BindSetMember"); 91 | 92 | var binderDefault = binder.FallbackSetMember(this, value); 93 | 94 | var missingLambda = Expression.Lambda(Expression.Call( 95 | GetClayBehavior(), 96 | IClayBehavior_SetMemberMissing, 97 | Expression.Lambda(binderDefault.Expression), 98 | GetLimitedSelf(), 99 | Expression.Constant(binder.Name, typeof(string)), 100 | Expression.Convert(value.Expression, typeof(object)))); 101 | 102 | var call = Expression.Call( 103 | GetClayBehavior(), 104 | IClayBehavior_SetMember, 105 | missingLambda, 106 | GetLimitedSelf(), 107 | Expression.Constant(binder.Name, typeof(string)), 108 | Expression.Convert(value.Expression, typeof(object))); 109 | 110 | var dynamicSuggestion = new DynamicMetaObject(call, BindingRestrictions.GetTypeRestriction(Expression, LimitType).Merge(binderDefault.Restrictions)); 111 | 112 | return binder.FallbackSetMember(this, value, dynamicSuggestion); 113 | } 114 | 115 | public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) 116 | { 117 | Logger.Log(LogLevel.Debug, null, "BindInvokeMember"); 118 | 119 | var argValues = Expression.NewArrayInit(typeof(object), args.Select(x => Expression.Convert(x.Expression, typeof(Object)))); 120 | var argNames = Expression.Constant(binder.CallInfo.ArgumentNames, typeof(IEnumerable)); 121 | var argNamedEnumerable = Expression.Call(typeof(Arguments).GetMethod("From"), argValues, argNames); 122 | 123 | var binderDefault = binder.FallbackInvokeMember(this, args); 124 | 125 | var missingLambda = Expression.Lambda(Expression.Call( 126 | GetClayBehavior(), 127 | IClayBehavior_InvokeMemberMissing, 128 | Expression.Lambda(binderDefault.Expression), 129 | GetLimitedSelf(), 130 | Expression.Constant(binder.Name, typeof(string)), 131 | argNamedEnumerable)); 132 | 133 | var call = Expression.Call( 134 | GetClayBehavior(), 135 | IClayBehavior_InvokeMember, 136 | missingLambda, 137 | GetLimitedSelf(), 138 | Expression.Constant(binder.Name, typeof(string)), 139 | argNamedEnumerable); 140 | 141 | var dynamicSuggestion = new DynamicMetaObject( 142 | call, BindingRestrictions.GetTypeRestriction(Expression, LimitType).Merge(binderDefault.Restrictions)); 143 | 144 | return binder.FallbackInvokeMember(this, args, dynamicSuggestion); 145 | } 146 | 147 | 148 | public override DynamicMetaObject BindConvert(ConvertBinder binder) 149 | { 150 | Logger.Log(LogLevel.Debug, null, "BindConvert"); 151 | 152 | var binderDefault = binder.FallbackConvert(this); 153 | 154 | var missingLambda = Expression.Lambda(Expression.Call( 155 | GetClayBehavior(), 156 | IClayBehavior_ConvertMissing, 157 | Expression.Lambda(Expression.Convert(binderDefault.Expression, typeof(object))), 158 | GetLimitedSelf(), 159 | Expression.Constant(binder.Type, typeof(Type)), 160 | Expression.Constant(binder.Explicit, typeof(bool)))); 161 | 162 | var call = Expression.Call( 163 | GetClayBehavior(), 164 | IClayBehavior_Convert, 165 | missingLambda, 166 | GetLimitedSelf(), 167 | Expression.Constant(binder.Type, typeof(Type)), 168 | Expression.Constant(binder.Explicit, typeof(bool))); 169 | 170 | var convertedCall = Expression.Convert(call, binder.ReturnType); 171 | 172 | var dynamicSuggestion = new DynamicMetaObject( 173 | convertedCall, BindingRestrictions.GetTypeRestriction(Expression, LimitType).Merge(binderDefault.Restrictions)); 174 | 175 | //return binder.FallbackConvert(this, dynamicSuggestion); 176 | return dynamicSuggestion; 177 | } 178 | 179 | 180 | public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) 181 | { 182 | Logger.Log(LogLevel.Debug, null, "BindUnaryOperation"); 183 | 184 | throw new NotImplementedException(); 185 | } 186 | 187 | public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) 188 | { 189 | Logger.Log(LogLevel.Debug, null, "BindGetIndex"); 190 | 191 | var a2 = Expression.NewArrayInit(typeof(object), indexes.Select(x => Expression.Convert(x.Expression, typeof(Object)))); 192 | 193 | var binderFallback = binder.FallbackGetIndex(this, indexes); 194 | 195 | var call = Expression.Call( 196 | GetClayBehavior(), 197 | IClayBehavior_GetIndex, 198 | Expression.Lambda(binderFallback.Expression), 199 | GetLimitedSelf(), 200 | a2); 201 | 202 | return new DynamicMetaObject(call, BindingRestrictions.GetTypeRestriction(Expression, LimitType).Merge(binderFallback.Restrictions)); 203 | } 204 | 205 | public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) 206 | { 207 | Logger.Log(LogLevel.Debug, null, "BindSetIndex"); 208 | 209 | var a2 = Expression.NewArrayInit(typeof(object), indexes.Select(x => Expression.Convert(x.Expression, typeof(Object)))); 210 | 211 | var binderFallback = binder.FallbackSetIndex(this, indexes, value); 212 | 213 | var call = Expression.Call( 214 | GetClayBehavior(), 215 | IClayBehavior_SetIndex, 216 | Expression.Lambda(binderFallback.Expression), 217 | GetLimitedSelf(), 218 | a2, 219 | Expression.Convert(value.Expression, typeof(object))); 220 | 221 | return new DynamicMetaObject(call, BindingRestrictions.GetTypeRestriction(Expression, LimitType).Merge(binderFallback.Restrictions)); 222 | } 223 | 224 | public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) 225 | { 226 | Logger.Log(LogLevel.Debug, null, "BindDeleteIndex"); 227 | 228 | throw new NotImplementedException(); 229 | } 230 | 231 | public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) 232 | { 233 | Logger.Log(LogLevel.Debug, null, "BindInvoke"); 234 | 235 | var argValues = Expression.NewArrayInit(typeof(object), args.Select(x => Expression.Convert(x.Expression, typeof(Object)))); 236 | var argNames = Expression.Constant(binder.CallInfo.ArgumentNames, typeof(IEnumerable)); 237 | var argNamedEnumerable = Expression.Call(typeof(Arguments).GetMethod("From"), argValues, argNames); 238 | 239 | var binderFallback = binder.FallbackInvoke(this, args); 240 | 241 | var call = Expression.Call( 242 | GetClayBehavior(), 243 | IClayBehavior_InvokeMember, 244 | Expression.Lambda(binderFallback.Expression), 245 | GetLimitedSelf(), 246 | Expression.Constant(null, typeof(string)), 247 | argNamedEnumerable); 248 | 249 | return new DynamicMetaObject(call, BindingRestrictions.GetTypeRestriction(Expression, LimitType).Merge(binderFallback.Restrictions)); 250 | } 251 | 252 | public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) 253 | { 254 | Logger.Log(LogLevel.Debug, null, "BindCreateInstance"); 255 | 256 | throw new NotImplementedException(); 257 | } 258 | 259 | public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) 260 | { 261 | Logger.Log(LogLevel.Debug, null, "BindUnaryOperation"); 262 | 263 | throw new NotImplementedException(); 264 | } 265 | 266 | public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) 267 | { 268 | Logger.Log(LogLevel.Debug, null, "BindBinaryOperation"); 269 | 270 | var binderFallback = binder.FallbackBinaryOperation(this, arg); 271 | 272 | var call = Expression.Call( 273 | GetClayBehavior(), 274 | IClayBehavior_BinaryOperation, 275 | Expression.Lambda(binderFallback.Expression), 276 | GetLimitedSelf(), 277 | Expression.Constant(binder.Operation, typeof(ExpressionType)), 278 | Expression.Convert(arg.Expression, typeof(object))); 279 | 280 | return new DynamicMetaObject(call, BindingRestrictions.GetTypeRestriction(Expression, LimitType)); 281 | } 282 | 283 | public override IEnumerable GetDynamicMemberNames() 284 | { 285 | Logger.Log(LogLevel.Debug, null, "GetDynamicMemberNames"); 286 | 287 | throw new NotImplementedException(); 288 | } 289 | } 290 | } -------------------------------------------------------------------------------- /src/ClaySharp/ClaySharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {76BCD43B-7BA5-4B63-B1E1-861641CA2686} 9 | Library 10 | Properties 11 | ClaySharp 12 | ClaySharp 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 | ..\packages\Castle.Core.3.3.3\lib\net40-client\Castle.Core.dll 36 | True 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 83 | -------------------------------------------------------------------------------- /src/ClaySharp/DefaultClayActivator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Dynamic; 4 | using System.Linq.Expressions; 5 | using Castle.DynamicProxy; 6 | 7 | namespace ClaySharp 8 | { 9 | public class DefaultClayActivator : IClayActivator 10 | { 11 | static readonly IProxyBuilder _builder = new DefaultProxyBuilder(); 12 | 13 | public dynamic CreateInstance(Type baseType, IEnumerable behaviors, IEnumerable arguments) 14 | { 15 | var isDynamicMetaObjectProvider = typeof(IDynamicMetaObjectProvider).IsAssignableFrom(baseType); 16 | var isClayBehaviorProvider = typeof(IClayBehaviorProvider).IsAssignableFrom(baseType); 17 | 18 | if (isDynamicMetaObjectProvider && isClayBehaviorProvider) 19 | { 20 | var constructorArguments = new object[] { behaviors }; 21 | return Activator.CreateInstance(baseType, constructorArguments); 22 | } 23 | 24 | Func contextualize = proxy => proxy; 25 | 26 | var options = new ProxyGenerationOptions(); 27 | var constructorArgs = new List(); 28 | if (!isClayBehaviorProvider) 29 | { 30 | var mixin = new MixinClayBehaviorProvider(behaviors); 31 | options.AddMixinInstance(mixin); 32 | constructorArgs.Add(mixin); 33 | } 34 | if (!isDynamicMetaObjectProvider) 35 | { 36 | var mixin = new MixinDynamicMetaObjectProvider(); 37 | options.AddMixinInstance(mixin); 38 | constructorArgs.Add(mixin); 39 | var prior = contextualize; 40 | contextualize = proxy => { mixin.Instance = proxy; return prior(proxy); }; 41 | } 42 | 43 | var proxyType = _builder.CreateClassProxyType(baseType, Type.EmptyTypes, options); 44 | 45 | constructorArgs.Add(new IInterceptor[] { new ClayInterceptor() }); 46 | if (arguments != null) 47 | constructorArgs.AddRange(arguments); 48 | 49 | return contextualize(Activator.CreateInstance(proxyType, constructorArgs.ToArray())); 50 | } 51 | 52 | class MixinClayBehaviorProvider : IClayBehaviorProvider 53 | { 54 | private readonly IClayBehavior _behavior; 55 | 56 | public MixinClayBehaviorProvider(IEnumerable behaviors) 57 | { 58 | _behavior = new ClayBehaviorCollection(behaviors); 59 | } 60 | 61 | IClayBehavior IClayBehaviorProvider.Behavior 62 | { 63 | get { return _behavior; } 64 | } 65 | } 66 | 67 | class MixinDynamicMetaObjectProvider : IDynamicMetaObjectProvider 68 | { 69 | public object Instance { get; set; } 70 | 71 | DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression expression) 72 | { 73 | return new ClayMetaObject(Instance, expression); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ClaySharp/IClayActivator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ClaySharp 5 | { 6 | public interface IClayActivator 7 | { 8 | dynamic CreateInstance(Type baseType, IEnumerable behaviors, IEnumerable arguments); 9 | } 10 | } -------------------------------------------------------------------------------- /src/ClaySharp/IClayBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace ClaySharp 6 | { 7 | public interface IClayBehavior 8 | { 9 | object GetMember(Func proceed, object self, string name); 10 | object SetMember(Func proceed, object self, string name, object value); 11 | object InvokeMember(Func proceed, object self, string name, INamedEnumerable args); 12 | object GetIndex(Func proceed, object self, IEnumerable keys); 13 | object SetIndex(Func proceed, object self, IEnumerable keys, object value); 14 | 15 | object GetMembers(Func proceed, object self, IDictionary members); 16 | 17 | object Convert(Func proceed, object self, Type type, bool isExplicit); 18 | object BinaryOperation(Func proceed, object self, ExpressionType operation, object value); 19 | 20 | object InvokeMemberMissing(Func proceed, object self, string name, INamedEnumerable args); 21 | object GetMemberMissing(Func proceed, object self, string name); 22 | object SetMemberMissing(Func proceed, object self, string name, object value); 23 | object ConvertMissing(Func proceed, object self, Type type, bool isExplicit); 24 | } 25 | 26 | public interface INamedEnumerable : IEnumerable 27 | { 28 | IEnumerable Positional { get; } 29 | IDictionary Named { get; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ClaySharp/IClayBehaviorProvider.cs: -------------------------------------------------------------------------------- 1 | namespace ClaySharp 2 | { 3 | public interface IClayBehaviorProvider 4 | { 5 | IClayBehavior Behavior { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/ClaySharp/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ClaySharp 4 | { 5 | public enum LogLevel 6 | { 7 | Debug, 8 | Information, 9 | Warning, 10 | Error, 11 | Fatal 12 | } 13 | 14 | public interface ILogger 15 | { 16 | bool IsEnabled(LogLevel level); 17 | void Log(LogLevel level, Exception exception, string format, params object[] args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ClaySharp/Implementation/Arguments.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | 7 | namespace ClaySharp.Implementation 8 | { 9 | public static class Arguments 10 | { 11 | public static INamedEnumerable FromT(IEnumerable arguments, IEnumerable names) 12 | { 13 | return new NamedEnumerable(arguments, names); 14 | } 15 | 16 | public static INamedEnumerable From(IEnumerable arguments, IEnumerable names) 17 | { 18 | return new NamedEnumerable(arguments, names); 19 | } 20 | 21 | class NamedEnumerable : INamedEnumerable 22 | { 23 | readonly IEnumerable _arguments; 24 | readonly IEnumerable _names; 25 | 26 | public NamedEnumerable(IEnumerable arguments, IEnumerable names) 27 | { 28 | if (arguments == null) 29 | { 30 | throw new ArgumentNullException("arguments"); 31 | } 32 | if (names == null) 33 | { 34 | throw new ArgumentNullException("names"); 35 | } 36 | if (arguments.Count() < names.Count()) 37 | { 38 | throw new ArgumentException("arguments.Count() < names.Count()"); 39 | } 40 | 41 | _arguments = arguments; 42 | _names = names; 43 | } 44 | 45 | IEnumerator IEnumerable.GetEnumerator() 46 | { 47 | return _arguments.GetEnumerator(); 48 | } 49 | 50 | IEnumerator IEnumerable.GetEnumerator() 51 | { 52 | return _arguments.GetEnumerator(); 53 | } 54 | 55 | IEnumerable INamedEnumerable.Positional 56 | { 57 | get { return _arguments.Take(_arguments.Count() - _names.Count()); } 58 | } 59 | 60 | IDictionary INamedEnumerable.Named 61 | { 62 | get { return new Named(_arguments, _names); } 63 | } 64 | 65 | class Named : IDictionary 66 | { 67 | private readonly IEnumerable _arguments; 68 | private readonly IEnumerable _names; 69 | 70 | private ICollection _argumentsCollection; 71 | private ICollection _namesCollection; 72 | 73 | public Named(IEnumerable arguments, IEnumerable names) 74 | { 75 | _arguments = arguments.Skip(arguments.Count() - names.Count()); 76 | _names = names; 77 | } 78 | 79 | IEnumerable> MakeEnumerable() 80 | { 81 | return _arguments.Zip(_names, (arg, name) => new KeyValuePair(name, arg)); 82 | } 83 | 84 | IEnumerator> IEnumerable>.GetEnumerator() 85 | { 86 | return MakeEnumerable().GetEnumerator(); 87 | } 88 | 89 | IEnumerator IEnumerable.GetEnumerator() 90 | { 91 | return MakeEnumerable().GetEnumerator(); 92 | } 93 | 94 | void ICollection>.Add(KeyValuePair item) 95 | { 96 | throw new NotImplementedException(); 97 | } 98 | 99 | void ICollection>.Clear() 100 | { 101 | throw new NotImplementedException(); 102 | } 103 | 104 | bool ICollection>.Contains(KeyValuePair item) 105 | { 106 | return MakeEnumerable().Contains(item); 107 | } 108 | 109 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) 110 | { 111 | throw new NotImplementedException(); 112 | } 113 | 114 | bool ICollection>.Remove(KeyValuePair item) 115 | { 116 | throw new NotImplementedException(); 117 | } 118 | 119 | int ICollection>.Count 120 | { 121 | get { return _names.Count(); } 122 | } 123 | 124 | bool ICollection>.IsReadOnly 125 | { 126 | get { return true; } 127 | } 128 | 129 | bool IDictionary.ContainsKey(string key) 130 | { 131 | return _names.Contains(key); 132 | } 133 | 134 | void IDictionary.Add(string key, T value) 135 | { 136 | throw new NotImplementedException(); 137 | } 138 | 139 | bool IDictionary.Remove(string key) 140 | { 141 | throw new NotImplementedException(); 142 | } 143 | 144 | bool IDictionary.TryGetValue(string key, out T value) 145 | { 146 | var pair = MakeEnumerable().FirstOrDefault(kv => kv.Key == key); 147 | 148 | // pair is a value type. in case of key-miss, 149 | // will default to key=(string)null,value=(object)null 150 | 151 | value = pair.Value; 152 | return pair.Key != null; 153 | } 154 | 155 | //TBD 156 | T IDictionary.this[string key] 157 | { 158 | get 159 | { 160 | return MakeEnumerable() 161 | .Where(kv => kv.Key == key) 162 | .Select(kv => kv.Value) 163 | .FirstOrDefault(); 164 | } 165 | set { throw new NotImplementedException(); } 166 | } 167 | 168 | ICollection IDictionary.Keys 169 | { 170 | get 171 | { 172 | return _namesCollection = _namesCollection ?? _names as ICollection ?? _names.ToArray(); 173 | } 174 | } 175 | 176 | ICollection IDictionary.Values 177 | { 178 | get { return _argumentsCollection = _argumentsCollection ?? _arguments as ICollection ?? _arguments.ToArray(); } 179 | } 180 | } 181 | } 182 | 183 | public static INamedEnumerable Empty() 184 | { 185 | return From(Enumerable.Empty(), Enumerable.Empty()); 186 | } 187 | } 188 | 189 | } -------------------------------------------------------------------------------- /src/ClaySharp/Nil.cs: -------------------------------------------------------------------------------- 1 | using ClaySharp.Behaviors; 2 | 3 | namespace ClaySharp 4 | { 5 | public static class Nil 6 | { 7 | static readonly object Singleton = new Clay(new NilBehavior(), new InterfaceProxyBehavior()); 8 | public static object Instance { get { return Singleton; } } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ClaySharp/NullLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ClaySharp 4 | { 5 | public class NullLogger : ILogger 6 | { 7 | private static readonly ILogger _instance = new NullLogger(); 8 | 9 | public static ILogger Instance 10 | { 11 | get 12 | { 13 | return _instance; 14 | } 15 | } 16 | 17 | public bool IsEnabled(LogLevel level) 18 | { 19 | return false; 20 | } 21 | 22 | public void Log(LogLevel level, Exception exception, string format, params object[] args) 23 | { 24 | 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/ClaySharp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Security; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DynShape")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("DynShape")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("496820b5-1fa9-484b-8d80-d33445f73496")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | [assembly: SecurityTransparent] -------------------------------------------------------------------------------- /src/ClaySharp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | --------------------------------------------------------------------------------