tag:blogger.com,1999:blog-24418503995403007102024-02-27T01:39:16.923-08:00CraigBlogCraig Andera's blog.Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.comBlogger646125tag:blogger.com,1999:blog-2441850399540300710.post-80872666777202974032022-02-13T09:02:00.002-08:002022-02-13T09:02:37.272-08:00My New Home on the Web<p>Perhaps obviously, the content on this blog is very old; I have not maintained it in many years. Nevertheless, I still occasionally have people looking for me wind up here. </p><p>If you would like to follow my adventures these days, the best place is <a href="https://getsmarterandmakestuff.com" target="_blank">Get Smarter and Make Stuff</a>, where I chronicle my adventures attempting to do those two things. It's an umbrella project for a blog, <a href="https://www.youtube.com/channel/UC1FfM9E-PG0JG7QKd9-IBVQ/">a YouTube channel</a>, and whatever else I decide to add.</p><p>As one note, it is NOT generally a programming project. I get enough of that at my job these days, so I focus more on building physical objects. But that line gets pretty blurry when you get into things like CNC, so I'm not saying it won't have ANY software content.</p>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-84603828018036445632012-05-04T08:14:00.001-07:002012-05-04T08:14:11.394-07:00ThinkRelevace: The Podcast, Episode 011The latest episode of the podcast is available in <a href="http://thinkrelevance.com/blog/2012/05/04/thinkrelevance-the-podcast-episode-011-jen-myers">the usual place</a>. On this one, we talk to Jen Myers, one of our designers. I've worked with her a few times now, and she's great. We had a fun conversation about the various things she's into, most notably <a href="http://girldevelopitcbus.com/">Girl Develop It Columbus</a>.<br />
<br />
Quite a long time elapsed between recording this episode and posting it. That certainly had nothing to do with my desire to get it out there, but other episodes had tighter timing considerations. So I'm very glad that the schedule cleared enough that we could finally get this one out there.<br />
<br />
Go have a listen, already!Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-62688945967170644712012-04-29T06:13:00.002-07:002012-04-29T06:13:40.891-07:00ThinkRelevance: The Podcast - Episodes 009 and 010I've been a bit remiss in posting here. I imagine most people that listen to the podcast are watching out for new episodes over at <a href="http://thinkrelevance.com/blog">the company blog</a>, anyway. Still, I wanted to call out these episodes because they're two of my favorites so far.<br />
<br />
First, on <a href="http://thinkrelevance.com/blog/2012/04/09/thinkrelevance-the-podcast-episode-009-alan-dipert">episode 009</a>, I got a chance to talk to Alan. We talked about Relevance's recent re-launch of our training efforts, which was cool. But my favorite part of the interview was the bit in the second half where Alan launches into a really interesting explanation of some academic papers he's been looking at lately. Good stuff.<br />
<br />
On <a href="http://thinkrelevance.com/blog/2012/04/26/thinkrelevance-the-podcast-episode-010-stu-halloway">episode 010</a>, I finally got to interview Stu Halloway. I've wanted to have Stu on since the beginning of the podcast. We talked about <a href="http://datomic.com/">Datomic</a>, of course, although not exclusively. You can tell by the extensive show notes that we covered a lot of ground.<br />
<br />
Both episodes were a lot of fun for me, and I'm already looking forward to having both of these guys on again.Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-43786828608877262642012-03-28T07:22:00.000-07:002012-03-28T07:22:11.658-07:00ThinkRelevance: The Podcast, Episode 008I realize we just released a podcast two days ago, but a) there hadn't been one for almost a month before that, and b) I had already recorded this one with <a href="http://blog.fogus.me/">Fogus</a> talking about <a href="http://himera.herokuapp.com/index.html">Himera</a>, his ClojureScript compilation web service. Himera just got a facelift and some new functionality, so it seemed like a good time to put out an episode where we talk a bit more about it.<br />
<br />
Of course, we didn't limit ourselves to just talking about ClojureScript. We also hit reading, German rock, and a bunch of other interesting stuff.<br />
<br />
<a href="http://thinkrelevance.com/blog/2012/03/28/thinkrelevance-the-podcast-episode-008-michael-fogus">Go listen</a>!Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-35780781506423579952012-03-26T07:07:00.000-07:002012-04-29T06:13:40.886-07:00ThinkRelevance: The Podcast, Episode 007I just published ThinkRelevance: the Podcast, episode <a href="http://thinkrelevance.com/blog/2012/03/26/thinkrelevance-the-podcast-episode-007-clinton-nixon">007</a> to the intertubes. Have a listen! We talk to Clinton Nixon (the man so awesome it took two presidents to name him), about his Ruby on Rails setup. Clinton is an interesting guy, and I had fun talking to him.Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-58543360989091418672012-02-28T05:47:00.000-08:002012-02-28T05:47:35.084-08:00ThinkRelevance: The Podcast, Episode 006The latest episode of ThinkRelevance: The Podcast is <a href="http://thinkrelevance.com/blog/2012/02/27/thinkrelevance-the-podcast-episode-006-larry-karnowski">up</a>. On this episode, I had a chance to talk to Larry Karnowski, mostly about Relevance culture. Relevance has a very interesting set of practices and traditions that are a large part of both why it's so fun to work there and why we're so good at what we do (if I do say so myself). Larry is one of the key keepers of that culture, and he does a great job of explaining what many of those practices and traditions are.<br />
<br />
Have a listen!Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-25382741098573583742012-02-03T14:14:00.000-08:002012-02-03T14:14:04.717-08:00ThinkRelevance: The Podcast, Episode 005<span style="background-color: white; font-family: arial, sans-serif; font-size: 13px;">Ever since I began thinking about doing a Relevance podcast, I knew that I was going to want to have Michael Parenteau on the show. Michael is head of our design department, and a designer himself. He has a great attitude, and brings both that and his considerable talent to everything he does. I happened to be down in Durham the week that we released the new website, which Michael had a big hand in bringing about, so I knew the time was right to have a conversation with him about it.</span> <br />
<span style="background-color: white; font-family: arial, sans-serif; font-size: 13px;"><br />
</span><br />
<span style="background-color: white; font-family: arial, sans-serif; font-size: 13px;">Check out the latest episode over at <a href="http://thinkrelevance.com/blog/2012/02/03/thinkrelevance-the-podcast-episode-005-michael-parenteau">the ThinkRelevance blog</a>!</span>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-7006422671102854712012-01-26T05:31:00.000-08:002012-02-28T05:44:38.238-08:00ThinkRelevance: The Podcast, Episode 004This time, I interview Aaron Bedra, who has left the company. And yes, we knew he was leaving when we did the podcast, which I think is really cool. I like working at a place where we feel comfortable posting interviews with people who have decided to go work somewhere else.<br />
<br />
Check out the episode <a href="http://thinkrelevance.com/blog/2012/01/25/thinkrelevance-the-podcast-episode-004-aaron-bedra-s-valedictory">here</a>.Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-8202949027145673302012-01-24T19:01:00.000-08:002012-01-24T19:01:39.537-08:00ShadowSpawn Version 0.2.2Version 0.2.2 of <a href="https://github.com/candera/shadowspawn">shadowspawn</a> is out. Go <a href="https://github.com/candera/shadowspawn/downloads">download</a> it!<br />
<br />
This version fixes issue <a href="https://github.com/candera/shadowspawn/issues/14">#14</a>, where shadowspawn wasn't correctly handling options that started with a dash. The biggest problem with that, of course, is that it stopped people from using shadowspawn together with <a href="http://en.wikipedia.org/wiki/Rsync">rsync</a>, which is a pretty killer combination. I use that setup myself, and it means I can do things like have dozens of full copies of my hard drive, but files that are the same from one backup to the other don't take up any additional space. Nice.Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com2tag:blogger.com,1999:blog-2441850399540300710.post-87424006014006565952012-01-12T12:27:00.000-08:002012-02-28T05:44:38.238-08:00ThinkRelevance: The Podcast, Episode 003 - Brenton Ashworth on ClojureScript One<span style="background-color: white; font-family: arial, sans-serif; font-size: 13px; line-height: 18px;">Episode 003 of ThinkRelevance: The Podcast is out. We talk to Brenton Ashworth about </span><span style="background-color: white; font-family: arial, sans-serif; font-size: 13px; line-height: 18px;"><a href="http://clojurescriptone.com/">ClojureScript One</a>. Check it out at </span><a href="http://thinkrelevance.com/blog/2012/01/12/podcast-episode-003.html">http://thinkrelevance.com/blog/2012/01/12/podcast-episode-003.html</a>.Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-61290830659799582842012-01-10T07:54:00.000-08:002012-01-10T07:54:49.540-08:00Look, Ma, I'm on the Polytechnicast!While I was in Minnesota over the holidays, I made a point of going to hang out with my good friend <a href="http://robstenzinger.com/">Rob Stenzinger</a>. We talked about nerd stuff, as we always do. One thing led to another, and before I knew it we were recording a screencast about Clojure. <a href="http://vimeo.com/34821778">Have a look</a>, and try not to laugh too much when I forget the semantics of set literals.Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-1417601981265419192012-01-10T07:51:00.000-08:002012-02-28T05:44:38.238-08:00ThinkRelevance: The Podcast, Episode 002 - David LiebkeI'm a little behind on posting this, but <a href="http://thinkrelevance.com/blog/2012/01/05/podcast-episode-002.html">the second episode of Think Relevance: The Podcast</a>, is out. In this one, we talk to David Liebke about Avout, a Clojure library for coordinating distributed state changes.Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-53828400725202481982011-12-25T21:18:00.001-08:002011-12-25T21:18:20.400-08:00CraigBlog is BackWell, it was painful, but I appear to have gotten the ol' blog back on the air, including all my old posts going back to 2003, and something like 90% of all comments. The formatting has not survived the trip intact, but everything I've looked at has been at least readable. Here's hoping that my new home is the last one I'll have for a long, long time.Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-15361577657496196822011-12-19T10:25:00.000-08:002011-12-19T11:55:01.794-08:00ThinkRelevance: The Podcast, Episode 001I recently had a chance to sit down with Justin Gehtland, CEO of Relevance, Inc. to talk about everything from how Relevance got started to why Relevance is a B Corporation.<br />
<br />
Check it out <a href="http://thinkrelevance.com/blog/2011/12/19/podcast-episode-001.html">at our company blog</a>.<br />
<br />
And yes, I'm still working on getting all the content from my old blog moved over here. I haven't hit any blockers yet, and have been making good progress.Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-34566720021619195552011-12-13T10:03:00.002-08:002012-03-26T07:10:47.576-07:00Interim PostUnfortunately, my old blog has rather unexpectedly gone off the air. I have all the old posts, and I'm scrambling to get them moved here, but it's going to be a little while before I can complete that process.<br />
<br />
Sorry about that.<br />
<br />
<b>Update: </b>I managed to get everything moved over. Links <b>should</b> be working, but if you're looking at this post, the chances are that the one you followed didn't. Might I suggest you search for it, including the term "CraigBlog"? The search engines should have indexed everything in its new location by now.<br />
<br />
If all else fails and you can't find something, feel free to email me at candera@wangdera.com and I'll dig up whatever it is you're looking for. Thanks, and sorry again!Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com2tag:blogger.com,1999:blog-2441850399540300710.post-18413837025383177472011-07-25T10:53:00.001-07:002011-12-25T07:05:01.041-08:00Announcing ShadowSpawn<p>One of the great things about working at <a href="http://thinkrelevance.com">Relevance</a> is having every Friday to work on non-client work. I've been spending mine on various open-source projects, like <a href="https://github.com/candera/artifact">Artifact</a> and a <a href="https://github.com/clojure/core.logic">core.logic</a> <a href="https://github.com/candera/reasoned-schemer">port</a> of <a href="http://mitpress.mit.edu/catalog/item/default.asp?ttype=2&tid=10663">The Reasoned Schemer</a>. But I haven't forgotten my Windows roots entirely. One of the things I've been trying to do is to continue to improve <a href="https://github.com/candera/hobocopy">hobocopy</a>, the open-source backup tool I wrote that can copy in-use files. I initially released it about five years ago, and in that time it has been downloaded something like 100,000 times. In the half year since I <a href="/community/blogs/craig/archive/2011/01/28/hobocopy-lives.aspx">re-launched it on github</a>, it has been downloaded around 15,000 times. So clearly, people are using it, and this is a reasonably successful open source project. </p><br /><p>But as the project has picked up steam again I've noticed that the things people want tend to fall into two categories: 1) fixes for mysterious issues with the Volume Shadow Service that I can't do much about, and 2) feature requests around the copying part of hobocopy. I'm sort of stuck on #1, but on #2 I could actually roll up my sleeves and implement copying of streams, or correctly enabling the SE_BACKUP privilege, or any one of the myriad other things that go into correctly copying a file from point A to point B; it's a surprising complex problem. </p><br /><p>Or I could cheat. </p><br /><p>For a long time now, I've been contemplating an alternate approach. Rather than have a piece of software that would make a shadow copy of a volume and then make copies out of it, I thought it would be handy to have a piece of software that would simply make a shadow copy, temporarily make it available on some drive letter, and then run an arbitrary command [1]. If that arbitrary command happens to be <a href="http://en.wikipedia.org/wiki/Robocopy">Robocopy</a>, then you've got all the advanced copying ability you could ever want. But you could just as easily use notepad if all you wanted to do was look at a file that was locked. </p><br /><p>Thus was born <a href="https://github.com/candera/shadowspawn">ShadowSpawn</a>. I worked with the ever-excellent <a href="http://blogs.msdn.com/b/kwolk/">Kim Wolk</a>, and this last Friday we got everything to the point where it works reasonably well. We've slapped a 0.1.0 version label on it and made it available for <a href="https://github.com/candera/shadowspawn/downloads">download</a>. I'm not using it in my nightly backups at home yet, the way I do with hobocopy (I plan to make the switch soon), so consider it to be beta: we expect issues, but it should generally work. </p><br /><p>Here's a simple example of how to use it: </p><br /><p style="padding-left: 30px;">shadowspawn C:\some\directory Q: robocopy Q: D:\some\other\directory</p><br /><p>That would snapshot your C: drive, mount the snapshotted version of C:\some\directory at Q:, run the robocopy command against it, and remove the Q: mapping when robocopy finished. More details are available at the <a href="https://github.com/candera/shadowspawn">website</a>.</p><br /><p>Anyway, we hope you find it useful. Feel free to ask for help on <a href="mailto:shadowspawn-tool@googlegroups.com">the mailing list</a>, or to make a feature request or bug report on <a href="https://github.com/candera/shadowspawn/issues">the issue tracker</a>.</p><br /><p>[1] Indeed, a tool called <a href="http://msdn.microsoft.com/en-us/library/bb530726(v=vs.85).aspx">VShadow</a> lets you do exactly that, albeit in a slightly different way. But I had additional reasons for wanting to take on this project. </p>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com1tag:blogger.com,1999:blog-2441850399540300710.post-86513528702991537992011-01-28T11:20:00.001-08:002011-12-25T07:05:01.042-08:00Hobocopy Lives!<p> </p><br /><p>It’s hard to believe it has already been almost over four years since I wrote <br />a little tool called hobocopy. The name was a pun on the truly-awesome <a href="http://en.wikipedia.org/wiki/Robocopy">robocopy</a>, which does a great <br />many things and is a truly useful tool. Hobocopy doesn’t do a tenth what <br />robocopy does, but it <strong>does</strong> do one thing that robocopy can’t: <br />copy files that are currently in use. It does that by using the Volume Shadow <br />Service, which is the same facility Windows uses to create Restore Points. You <br />can read more about hobocopy <a href="/community/blogs/craig/archive/2006/09/20/38362.aspx">here</a> <br />and <a href="/community/blogs/craig/archive/2007/01/08/45624.aspx">here</a>.</p><br /><p>I worked on hobocopy for a while, but as often happens, I got pulled onto <br />other things. I tried to see if someone else would pick up the torch, but was <br />unable to find anyone. Still, at least some people were able to get some use out <br />of it: there have been something like 100,000 downloads from SourceForge as of <br />this writing, and I get regular but infrequent questions about its use. So I <br />felt bad that I had let development languish. </p><br /><p>Well, now I work for <a href="http://thinkrelevance.com/">Relevance</a>, and <br />they have <a href="http://howwework.thinkrelevance.com/developer_narrative.html#open_source_fridays">a <br />very progressive policy on open source work</a>. Of course I have far more <br />projects I want to tackle than I could do in a decade of Fridays, but still, I <br />figured that hobocopy deserves some love. So I’ve been spending a bit of time on <br />it lately. I've created the <a href="http://groups.google.com/group/hobocopy">hobocopy Google group</a>, and I've moved the code to <a href="http://candera.github.com/hobocopy">its <br />new home on GitHub</a>. </p><br /><p>As you might imagine, there’s still a lot to do, but I’ll be <br />chipping away at it as I’m able to. Help of any sort (including bug reports!) <br />is, of course, welcome. </p><br /><p> </p>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com11tag:blogger.com,1999:blog-2441850399540300710.post-37639070040951427722010-12-24T09:03:00.001-08:002011-12-25T07:05:01.042-08:002010 - The Year in Review<p><br /></p><p>Forgive me, Internet, for I have sinned. It has been way too long since my last blog post. But I like to have an end-of-year post (here's <a href="/community/blogs/craig/archive/2010/01/05/2009-the-year-in-review.aspx">last year's</a>), so here at the end of 2010 seems an appropriate time to toss out something about what's been going on. This past year was ridiculously full, both from a personal and a professional standpoint. Any one of several of things that happened would have made the year notable.</p><br /><p>For starters, we bought a new house and moved into it in June. I love the new place, but of course moving is always difficult, and months later we still have a significant amount of unpacking to do. By itself, moving would have been enough, but my wife and I seem to have a propensity for doubling (or tripling, or quadrupling) down. So at the same time we were moving, we did a major renovation of and rented out our old place, helped my wife's parents move, and took a cruise to Alaska. Right after that, my oldest daughter started Kindergarten, which was quite a shock to the daily routine.</p><br /><p>Simultaneously with the move, I finished up at MSDN, having helped to ship VS2010. Having spent almost all of the last decade working in .NET, it made it a great time to make a major change. None of you who read this blog will be surprised that I wanted that change to involve doing <a href="http://clojure.org">Clojure</a>. Clojure is a really interesting language, but also a very new one. We're seeing lots of growth right now, but of course it's a small market compared to what we hope it will be in a year or three. So I was fortunate that I was able to find some Clojure consulting work at <a href="http://thinkrelevance.com">Relevance</a>. </p><br /><p>Relevance is a fascinating place. Even aside from the opportunity to do Clojure on a daily basis, I find it to be truly different from anywhere else I've ever worked. They take Agile seriously and (IMO) do it right. And where they don't do it right, well, they have a process for changing the process. It quite literally took me weeks to get used to the pace at which they get stuff done: it is a high-intensity, hard-core, hacker (in the best sense of that word) culture where they also manage to keep doing the right thing by the customer a top priority.</p><br /><p>Which is why I am pleased to announce that I will be starting there as a full-time employee on January 3rd. See, I told you that 2010 was a big year: among all the other stuff, it also marks the end of an eleven-year span of being an independent consultant. It was a great run, but I'm genuinely excited about the opportunity to work at Relevance in a full-time capacity. I simply could not pass up the chance to participate in the unique combination of culture, technology, and people. And the people include, of course, my good friend <a href="/community/blogs/tewald/">Tim Ewald</a>, who started at Relevance last month. This marks the fourth time we have worked together at one company or another, but the first time we've ever both been employees at the same time.</p><br /><p>So 2010 was a year of big changes for me and my family, almost all of them good. What will 2011 bring? I have no idea, but I'm going into it in a pretty good mood. :) </p><br />Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-4890422955545977872010-09-25T05:50:00.001-07:002011-12-25T07:05:01.042-08:00An Updated Nerd’s Halloween<p>[<strong>Update: </strong>corrected a minor typo. Also, I find it heartening that Peter is 70% of the way to his donation goal. Thanks to those that contributed.]</p> <p>I don’t blog much any more. Partly that’s just blog fatigue (I started in 2003), and partly that’s having two little kids (one of whom just started Kindergarten), a new gig (at <a href="http://thinkrelevance.com">Relevance</a>! Doing <a href="http://clojure.org">Clojure</a>!), and having just moved to a new house. But I came across something that’s a pretty interesting, and a bit too long to do full justice to on <a href="http://twitter.com/craigandera">my Twitter account</a>.</p> <p>Once upon a time, in a small way, <a href="http://www.pluralsight-training.net/community/blogs/craig/archive/2005/01/14/4958.aspx">I helped a guy make an awesome Halloween</a>. He emailed me the other day to let me know about his latest project, which is even crazier and more amazing. Check out the picture of what he’s going to try to do: </p> <p><img src="http://www.socalhalloween.com/Images/2010/Revised_Drill_in_Driveway_PL_PM_640x480.jpg" /> </p> <p>But he needs some help raising funds. In his own words: </p> <blockquote> <p>Craig,</p> <p>It's me again, the guy who likes Halloween more than most humans! This was the post you did on your blog: </p> <p><a href="http://www.pluralsight.com/community/blogs/craig/archive/2008/10/01/you-think-you-like-halloween.aspx">http://www.pluralsight.com/community/blogs/craig/archive/2008/10/01/you-think-you-like-halloween.aspx</a></p> <p>If you don't remember me [<em>Ed: how could I forget?!</em>], check out the quoted material at the bottom of this email. I noticed that you're not blogging much lately, but you are using Twitter. I was hoping you could blog and/or tweet about a project I'm trying to do this year. I'm trying to get the word out about my fund raising efforts to build this giant steampunk drilling machine prop (see attached photo) for Halloween this year. Someone who had emailed me on my site (<a href="http://www.socalhalloween.com/">www.SoCalHalloween.com</a> ) a couple of years ago created this entry on his blog:</p> <p> <br /><a href="http://superpunch.blogspot.com/2010/09/help-fund-building-of-giant-steampunk.html">http://superpunch.blogspot.com/2010/09/help-fund-building-of-giant-steampunk.html</a></p> <p> <br />A plug on your blog (or a tweet) would really help.</p> <p> <br />I'm using a site called "Kickstarter", which is designed to help do fund raising for any sort of creative project, using Amazon for payments. The rules are really simple: If you make or exceed your funding goal by the specified deadline, you get the money. If you don't make the goal, you get nothing. In my case, my deadline is October 14, so there are 21 days to raise the money. Also, people get gifts to thank them for pitching in. Any amount is helpful. $10? Sounds great. $20? Even better. $50? Say, you *do* care!</p> <p> <br />Please go to my Kickstarter page for Halloween 2010 to get more information, see how the fund raising works, and (hopefully) donate if you're so inclined: <a href="http://www.kickstarter.com/projects/1163328321/halloween-2010-journey-from-the-center-of-the-eart">http://www.kickstarter.com/projects/1163328321/halloween-2010-journey-from-the-center-of-the-eart</a> <br /></p> <p>If there are any problems with line wrapping, here's a shortened URL that will take you to the same page: <a href="http://tinyurl.com/2879mqk">http://tinyurl.com/2879mqk</a> <br /></p> <p>I have also modified the main page of the SoCalHalloween website to carry the video, a link to the Kickstarter page, and some pictures. It also has all the rest of the video and pictures that are always available: <a href="http://www.socalhalloween.com">http://www.socalhalloween.com</a></p> </blockquote> <p>So if, like I do, you think this is a really great idea, consider helping Peter out with a few bucks. Thanks! </p>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com0tag:blogger.com,1999:blog-2441850399540300710.post-55762496075883800762010-04-09T10:45:00.001-07:002011-12-25T07:05:01.042-08:00Clojure Concurrency Tutorial Available<p><strong>Update: </strong>all six parts are now available. That’s over two and a half hours of Clojurey goodness. </p> <p><strong>Update:</strong> There are now downloadable versions available in wmv and mp4 formats in a variety of resolutions. Just click the download button by each module. Also, the refs module is now up. </p> <p>A while back, I wrote up some slides on concurrent programming in <a href="http://clojure.org/">Clojure</a> that I used at <a href="http://codebetter.com/blogs/matthew.podwysocki/archive/2010/01/14/ann-dc-alt-net-1-27-2010-clojure.aspx">a DC ALT.NET presentation</a>. Since I had them ready, and since Pluralsight has a great infrastructure for doing online training (it’s called <a href="http://www.pluralsight-training.net/microsoft/default.aspx">Pluralsight On-Demand!</a><em></em>), it made sense for me to record the content and make it available. So I have. </p> <p>I’m happy to announce the immediate availability of the first four parts of my six-part Clojure concurrency tutorial, a screencast. You can find it <a href="http://link.pluralsight.com/clojure">here</a>. It is freely available, and no registration is required. The final two parts will be available in less than a week. </p> <p>I wrote the tutorial from the perspective of a .NET programmer who is new to Clojure. I don’t focus on the language itself, but rather the concurrency support in it. However, I don’t assume any prior knowledge of Clojure, so we start by covering just enough language syntax to get us through the demos. From there we dive into vars, atoms, agents, refs, promises, futures, and all sorts of good stuff. </p> <p>If you’ve been reading my blog, you’ll know I’m a big fan of Clojure. One of the reasons for this - but not the only reason - is its fantastic support for concurrent programming. I like to say that it provides “sanity in the face of concurrency”. If you’re interested to learn more, check out <a href="http://link.pluralsight.com/clojure">the tutorial</a>, and please – let me know what you think!</p>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com4tag:blogger.com,1999:blog-2441850399540300710.post-41460943657636857272010-04-02T12:23:00.001-07:002011-12-25T07:05:01.042-08:00The Zero-Argument Overload of Any<p>I'm a pretty big fan of Linq. I use it all the time in my C# code. But it's a relatively big library, and I've never studied it exhaustively, so from time to time I come across some neat corner of it I've never seen before. In fact, that happened just the other day. </p><br /><p>I find that I'll frequently write test code that looks like this: </p><br /><p>Assert.AreEqual(0, collection.Count()); </p><br /><p>or the inverse: </p><br /><p>Assert.AreNotEqual(0, collection.Count()); </p><br /><p>I've done it often enough that I have thought I ought to write a pair of extension methods on Assert called IsEmpty and IsNotEmpty. That's probably still a good idea, but it also turns out that the Linq extension method Any (normally used to test if at least one element of an enumeration meets some criteria) also has a zero-overload method that simply returns true if the enumeration has any elements at all. So I could rewrite the above tests as: </p><br /><p>Assert.IsFalse(collection.Any());</p><br /><p>and</p><br /><p>Assert.IsTrue(collection.Any());</p><br /><p>respectively. While having Assert.IsEmpty and Assert.IsNotEmpty extension methods would be better (perhaps your test framework already provides them), this is still a handy little thing to know about. </p>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com7tag:blogger.com,1999:blog-2441850399540300710.post-51227626964226360932010-03-17T07:44:00.001-07:002011-12-25T07:05:01.042-08:00Implementations of Slideshow in C# and Clojure<p>As I promised I would <a href="http://www.pluralsight.com/community/blogs/craig/archive/2010/03/10/creating-a-lazy-enumeration-of-directory-descendants-in-c.aspx">the other day</a>, I went ahead and implemented my stupid little slideshow app in both C# and <a href="http://clojure.org">Clojure</a>. It was an interesting exercise, the more so because I’m not entirely sure what conclusions to draw. I may have more to say about this at some point in the future, but for now I thought I’d just put up the code and share some general observations. So, you can find the C# version <a href="http://gist.github.com/335331">here</a>, and the Clojure version <a href="http://gist.github.com/335332">here</a>. </p><br /><p>The app is pretty simple. It starts a low-priority thread that lazily populates a list of filenames that are descendants of a particular directory. Meanwhile, a timer procedure randomly selects one of the filenames out of the ones that have been found so far and invalidates the screen. The repaint simply draws the image centered on a fullscreen window with a black background. If no images have been found yet, then it prints “Working…” on the screen instead. </p><br /><p>The Clojure version isn’t really an app, because I wrote all this code really quickly and didn’t bother giving it a main() function, but other than that they’re about as similar as I could make them, given that one is running against Swing on the JVM and the other against Windows Forms on the CLR. I suppose I could have written the Clojure one against Windows Forms as well using <a href="http://github.com/richhickey/clojure-clr">ClojureCLR</a>, but honestly it was simply more interesting to me to use Swing, as I’m not familiar with it. And this is anything but a scientific experiment. </p><br /><p>For the C# version, I eschewed the Forms designer to keep it closer to the spirit of the Clojure one. And for something like this, where I’m just blasting raw pixels at the screen, I think that was a reasonable choice. </p><br /><p>I wrote the Clojure version first. Perhaps that was a bad choice, since it meant I spent a lot more time on the Clojure one, since I had to figure out all the little details, like how to center an image in a region. It certainly made the C# version a lot easier to write, since I was pretty much just translating. But the C# version probably would have been easier for to write in any event, since I’ve been writing C# professionally for almost 10 years, and Clojure in my spare time for less than two.</p><br /><p>One thing that surprised me a bit is that the Clojure version turned out to be not much shorter than the C# version. Indeed, if I had put closing braces on the same line as the statement they close (which is like the Clojure style I use), I’d bet they’re about the same. I had expected it to be quite a bit more compact, since, as I’ve said before, my experience with Clojure has been that it generally lets you be more concise. In this case, I suspect it has to do with the fact that I had a lot of Swing-related code, which doesn’t lend itself strongly to the sorts of concision that Clojure supports. Or it could be the fact that I’m a Clojure n00b, and just don’t know how to structure the code to hit a good balance of terseness with readability. Certainly, I could make it a bit shorter by inlining calls to things like make-frame. Other recommendations as to how better to write either version would be more than welcome. </p><br /><p>Both versions of the code are pretty rough, as I wrote them essentially while I was waiting for stuff to install on a VM. I’d definitely clean them up more if I were going to do anything with them, but I figured it was better to get it up here than to wait potentially forever to get it polished. Really, I’m just trying to point out that not all my code looks this crappy. :) </p><br /><p>You’ll note that in the Clojure version, I use an atom to store the list of filenames. That’s not because I’m taking advantage of Clojure’s awesome concurrency support, it’s because it was a convenient thing to do. Note that the C# version doesn’t bother with synchronization at all, since I’ve only got two threads, and one is only reading, and one is only appending. If I wanted to get fancier with the threads (e.g. have another thread doing look-ahead loading of the next image), then I might have to start doing synchronization. I’d be in good shape on the Clojure side then, since not much would have to change, and the fact that Clojure values are immutable really helps. I don’t think the C# version wouldn’t need too much work, but I’d have to think about it more to be sure, and it would depend on exactly what I was trying to do. </p><br /><p>It’s apropos of nothing, but it made me smile that the C# version required PInvoke to get a fullscreen window, and the Swing one just supported it out-of-the-box. </p><br /><p>Being familiar with Visual Studio was a definite benefit when working with the C# code. I’m not very facile with the debugging capabilities of Emacs/SLIME, which is what I use to develop Clojure, and when I had problems it took me a little longer to figure them out. That said, having the REPL was really awesome, and it really makes me hate the Immediate Window in Visual Studio by comparison. It would be interesting to try <a href="http://www.bestinclass.dk/index.php/2010/03/clojure-ides-the-grand-tour-getting-started/">some of the other Clojure IDEs</a> to see how they stack up.</p><br /><p>Neither version of the code has much in the way of comments. Sorry about that. :)</p><br /><p>Anyway, that’s my pile of unstructured thoughts. Hope it’s of interest, if not value, to at least some of you. And as I said, further discussion, rewrites, comments, and criticism are wholly welcome.</p><br /><div><br /><script src="http://gist.github.com/335331.js"></script><br /></div>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com6tag:blogger.com,1999:blog-2441850399540300710.post-13929549397872000842010-03-10T03:57:00.001-08:002011-12-25T07:05:01.043-08:00Creating a Lazy Enumeration of Directory Descendants in C#<p><strong>Update: </strong>it <a href="http://www.pluralsight-training.net/community/blogs/craig/archive/2010/03/10/75386.aspx#comments">turns out that Clojure has a built-in file-seq function</a> (thanks to Stuart Sierra for pointing this out) that already does exactly this, making Clojure the winner hands-down for brevity :) Seriously though, arguments about “batteries included” aside – although a valid point of consideration – I still think it’s interesting to consider the bits of code below. </p> <p>Yesterday, I <a href="http://www.pluralsight-training.net/community/blogs/craig/archive/2010/03/09/creating-a-lazy-sequence-of-directory-descendants-in-clojure.aspx">posted some Clojure code</a> that showed how to create a function that would lazily return all the descendants of a directory. Here’s the code again: </p> <pre>(import [java.io File])<br /><br />(defn dir-descendants [dir]<br /> (let [children (.listFiles (File. dir))]<br /> (lazy-cat <br /> (map (memfn getPath) (filter (memfn isFile) children)) <br /> (mapcat dir-descendants <br /> (map (memfn getPath) (filter (memfn isDirectory) children))))))</pre><br /><br /><p>Towards the end of the post, I tossed off a somewhat off-hand comment: </p><br /><br /><blockquote><br /> <p>Two things struck me about writing this code. The first is that it’s really, really compact. It would be even smaller if it weren’t for the calls to memfn, which is just there so I can use a Java method as if it were a Clojure function. The second is that it was a bit of a mind-bender to write, although having written it I find it fairly easy to read. But I suspect both of those things would also be true if I attempted to write the equivalent C# (including the laziness, probably via a “yield return” implementation of IEnumerable somewhere), although it would almost certainly be at least somewhat more verbose. </p><br /></blockquote><br /><br /><p>Well, <a href="http://www.sellsbrothers.com/news/showTopic.aspx?ixTopic=2334">Chris Sells called me on that one</a>. Here’s the equivalent C# code he came up with: </p><br /><br /><pre>using System.IO;<br />using System.Collections.Generic;<br /><br />namespace ConsoleApplication1 {<br /> class Program {<br /> static IEnumerable<string> GetDirectoryDecendants(string path) {<br /> foreach (var file in Directory.GetFiles(path)) { yield return file; }<br /> foreach (var directory in Directory.GetDirectories(path)) {<br /> foreach (var file in GetDirectoryDecendants(directory)) { yield return file; }<br /> }<br /> }<br /> }<br />}</string></pre><br /><br /><p>I think quantitative comparisons of code terseness are generally pretty silly, but - subjectively - when I look at that it seems just as compact as the Clojure version. So I stand corrected on that one. A few other observations: </p><br /><br /><ul><br /> <li>Chris included namespace and class statements. Clojure has equivalents for these, but I omitted them. Omitting them from the C# version would make it even shorter. </li><br /><br /> <li>The C# version can actually be made <strong>more</strong> lazy than the Clojure one because of the CLR’s Directory.EnumerateFiles method, as pointed out by John <a href="http://www.pluralsight-training.net/community/blogs/craig/archive/2010/03/09/creating-a-lazy-sequence-of-directory-descendants-in-clojure.aspx#comments">in the comments to yesterday’s post</a>. That’s pretty cool. I could, however, take advantage of this if I used <a href="http://wiki.github.com/richhickey/clojure-clr/">ClojureCLR</a> instead of the JVM-based Clojure. </li><br /><br /> <li>I said that writing the Clojure version was a bit of a mind-bender. To clarify, the bendiness came from the behavior of lazy-cat, and I think primarily because I’m relatively new to Clojure. The “yield return” in the C# code – which is the key to the laziness – gave me similar pause when I first encountered it years ago. And although I’m comfortable enough with it now, the two yield return statements in Chris’s code made me pause for a moment to convince myself it would work without consuming stack. So I think there’s probably some inherent complexity in the laziness that – like many things – is no big barrier once you internalize it in either language. </li><br /></ul><br /><br /><p>So I haven’t proven anything to anyone, not even myself. That said, having worked with Clojure a bit and C# a <strong>lot</strong>, I’m still left with the impression that Clojure is generally more compact. It will be a few years before I can say whether that’s a good thing or not (or even whether it’s true), since I tend to think working with a language on that time scale is the only way to really get to know it. I will do two things, however. The first is to show you another bit of code in both Clojure and C#. First, the C#: </p><br /><br /><pre>private static IEnumerable<int> Fibs()<br />{<br /> yield return 0; <br /> yield return 1; <br /> <br /> int a = 0; <br /> int b = 1; <br /> while (true)<br /> {<br /> int temp = a + b; <br /> a = b; <br /> b = temp; <br /> yield return b; <br /> }<br />}</int></pre><br /><br /><p>Next, the Clojure:</p><br /><br /><pre>(def fibs (lazy-cat [0 1] (map + fibs (rest fibs))))</pre><br /><br /><p>Hopefully it’s obvious from at least one of these that they’re meant to compute the Fibonacci sequence. </p><br /><br /><p>Maybe someone can come up with ways to make the C# more compact: I would welcome such suggestions. And I’m not trying to have a Clojure-versus-C# contest here – those are stupid, like saying, “Which is better: a hammer or a drill press?” But language comparisons are still interesting in that you generally wind up learning something you didn’t know about at least one of the languages. </p><br /><br /><p>The other thing I’m going to do is to implement my slideshow app (the thing that had me writing the directory recursion function in the first place) in both languages and post them here. Until Chris posted, I was pretty sure I knew how they’d stack up. Now I’m just curious! Maybe you are, too. </p><br /><br /><p>Thanks, Chris, for keeping me honest. </p>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com3tag:blogger.com,1999:blog-2441850399540300710.post-28964744799759587242010-03-09T04:23:00.001-08:002011-12-25T07:05:01.043-08:00Creating a Lazy Sequence of Directory Descendants in Clojure<p><b>Update: </b>it turns out Clojure has a built-in function file-seq that does (almost) exactly this. The difference is that you get a lazy sequence of File objects, not paths. Hooray for "batteries included"!</p><br /><p>I was playing around with Clojure this morning, attempting to write a little photo viewer application. As part of this, I wound up writing a function that produces a lazy sequence of all the files that are descendants of a given directory. I was pretty happy with how it turned out, so I thought I’d share it. Here’s the code in its entirety: </p><br /><div><br /><pre>(import [java.io File])<br /><br />(defn dir-descendants [dir]<br /> (let [children (.listFiles (File. dir))]<br /> (lazy-cat <br /> (map (memfn getPath) (filter (memfn isFile) children)) <br /> (mapcat dir-descendants <br /> (map (memfn getPath) (filter (memfn isDirectory) children))))))<br /></pre><br /></div><br /><div></div><br /><div>The key bit here is the lazy-cat, which returns a lazy sequence – it doesn’t evaluate the second expression (in which I walk into the children of the current directory) until someone asks for the next element in the sequence. In other words, even though this function looks recursive, it’s not. Because lazy-cat returns right away, the “nested” call to dir-descendants happens after the stack has been popped. This is totally awesome, because it means I can call dir-descendants and it’ll return right away. So I can start displaying pictures without having to wait for the entire directory tree to be enumerated. </div><br /><div></div><br /><div>Two things struck me about writing this code. The first is that it’s really, really compact. It would be even smaller if it weren’t for the calls to memfn, which is just there so I can use a Java method as if it were a Clojure function. The second is that it was a bit of a mind-bender to write, although having written it I find it fairly easy to read. But I suspect both of those things would also be true if I attempted to write the equivalent C# (including the laziness, probably via a “yield return” implementation of IEnumerable somewhere), although it would almost certainly be at least somewhat more verbose. </div><br /><div></div><br /><p> </p>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com12tag:blogger.com,1999:blog-2441850399540300710.post-78226149155708825822010-02-24T03:13:00.001-08:002011-12-25T07:05:01.043-08:00On the Utility of TestPropertyAttribute<p>On the project I’ve been working on lately, I’ve been using the unit test stuff that ships with Visual Studio/.NET, in the Microsoft.VisualStudio.TestTools.UnitTesting namespace. For my purposes, I’ve found it to be adequate, and it has the benefit that I don’t need to include any additional libraries. </p> <p>I recently came across one use of it, however, that took me some time to figure out. Primarily because the documentation on this feature sucks pretty bad. It’s the TestPropertyAttribute, and it’s useful enough to be worth knowing about. So I thought I would share. </p> <p>If you read <a href="http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.testpropertyattribute(VS.80).aspx">the documentation for TestPropertyAttribute</a>, your first reaction might well be the same as mine was: “Huh? That looks like it adds precisely <strong>no</strong> value over any other custom attribute I might write.” Because the docs show that to retrieve the value, you reflect against the test method it’s defined on, which is pretty silly. However, in experimenting, I found that there’s <a href="http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.testcontext.properties(VS.80).aspx">an equally poorly documented property on the TestContext class</a> called Properties that you can use to get the value of this attribute. </p> <p>Still, if you only need a value within the TestMethod itself, there’s not much value in it, as it’s no better than just hardcoding the value within the body of the test. Where TestProperty shines, though, is that the properties for the current test are also available during the TestInitialize method. That means you can bung a whole bunch of common setup code into your TestInitialize method, and parameterize its behavior based on properties defined on the tests themselves. A little code might help demonstrate what I mean: </p> <span class="Apple-style-span" style="widows: 2; text-transform: none; text-indent: 0px; border-collapse: separate; font: medium 'Times New Roman'; white-space: normal; orphans: 2; letter-spacing: normal; color: rgb(0,0,0); word-spacing: 0px; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px"><span class="Apple-style-span" style="font-family: consolas, 'Courier New', courier, monospace; font-size: small"> </span></span><pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"><br class="Apple-interchange-newline" /> 1: </span><span class="kwrd" style="color: rgb(0,0,255)">using</span> System;</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 2: </span><span class="kwrd" style="color: rgb(0,0,255)">using</span> System.IO; </pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 3: </span><span class="kwrd" style="color: rgb(0,0,255)">using</span> Microsoft.VisualStudio.TestTools.UnitTesting;</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 4: </span> </pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 5: </span><span class="kwrd" style="color: rgb(0,0,255)">namespace</span> Demo</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 6: </span>{</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 7: </span> [TestClass]</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 8: </span> <span class="kwrd" style="color: rgb(0,0,255)">public</span> <span class="kwrd" style="color: rgb(0,0,255)">class</span> ThingTests</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 9: </span> {</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 10: </span> <span class="rem" style="color: rgb(0,128,0)">// Note that we have to have a TestContext, and it must</span></pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 11: </span> <span class="rem" style="color: rgb(0,128,0)">// be named TestContext</span></pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 12: </span> <span class="kwrd" style="color: rgb(0,0,255)">public</span> TestContext TestContext { get; set; }</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 13: </span> </pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 14: </span> <span class="rem" style="color: rgb(0,128,0)">// A simple field that we're going to initialize to </span></pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 15: </span> <span class="rem" style="color: rgb(0,128,0)">// some value in the Initialize method. </span></pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 16: </span> <span class="kwrd" style="color: rgb(0,0,255)">private</span> <span class="kwrd" style="color: rgb(0,0,255)">string</span> _thing; </pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 17: </span> </pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 18: </span> [TestInitialize]</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 19: </span> <span class="kwrd" style="color: rgb(0,0,255)">public</span> <span class="kwrd" style="color: rgb(0,0,255)">void</span> Initialize()</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 20: </span> {</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 21: </span> <span class="rem" style="color: rgb(0,128,0)">// Obviously we could do something much more complex here.</span></pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 22: </span> <span class="kwrd" style="color: rgb(0,0,255)">if</span> (TestContext.Properties.Contains(<span class="str" style="color: rgb(0,96,128)">"thing"</span>))</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 23: </span> {</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 24: </span> _thing = TestContext.Properties[<span class="str" style="color: rgb(0,96,128)">"thing"</span>] <span class="kwrd" style="color: rgb(0,0,255)">as</span> <span class="kwrd" style="color: rgb(0,0,255)">string</span>;</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 25: </span> }</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 26: </span> <span class="kwrd" style="color: rgb(0,0,255)">else</span></pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 27: </span> {</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 28: </span> _thing = <span class="str" style="color: rgb(0,96,128)">"default"</span>; </pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 29: </span> }</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 30: </span> }</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 31: </span> </pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 32: </span> [TestMethod]</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 33: </span> <span class="kwrd" style="color: rgb(0,0,255)">public</span> <span class="kwrd" style="color: rgb(0,0,255)">void</span> TestAgainstDefaultSetup()</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 34: </span> {</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 35: </span> Assert.AreEqual(<span class="str" style="color: rgb(0,96,128)">"default"</span>, _thing); </pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 36: </span> }</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 37: </span> </pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 38: </span> [TestMethod]</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 39: </span> [TestProperty(<span class="str" style="color: rgb(0,96,128)">"thing"</span>, <span class="str" style="color: rgb(0,96,128)">"non-default"</span>)]</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 40: </span> <span class="kwrd" style="color: rgb(0,0,255)">public</span> <span class="kwrd" style="color: rgb(0,0,255)">void</span> TestAgainstCustomSetup()</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 41: </span> {</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 42: </span> Assert.AreEqual(<span class="str" style="color: rgb(0,96,128)">"non-default"</span>, _thing); </pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 43: </span> }</pre><br /><br /> <pre style="background-color: rgb(255,255,255); margin: 0em; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 44: </span> }</pre><br /><br /> <pre class="alt" style="background-color: rgb(244,244,244); margin: 0em; width: 1248px; font-family: consolas, 'Courier New', courier, monospace; color: black; font-size: small"><span class="lnum" style="color: rgb(96,96,96)"> 45: </span>}</pre><br /><br /> <br /> Basically, we’ve got a field _thing that we usually want to leave at the default value of “default”, but in some tests we want to override to some other value. Moreover, we want to override it in the Initialize method, since that’s where we’re doing our setup that’s common across tests. – maybe we have fairly complicated setup that needs to differ only slightly in some tests. So we use the TestProperty attribute to set a property named “thing” to whatever we like. Then, in the Initialize method, we can use TestContext.Properties to check for the presence of that value and take appropriate action. <br /><br /><p>I’ve made the code intentionally trivial to more easily demonstrate the concept, but I’m sure you can visualize more realistic scenarios where this might come in handy. For example, I’ve used it to customize which directory my Initialize method operates on.</p>Craig Anderahttp://www.blogger.com/profile/17084199593129216563noreply@blogger.com8