Click here to Skip to main content
15,867,453 members
Articles / Operating Systems / Windows
Article

Rhino Mocks 2.2

Rate me:
Please Sign up or sign in to vote.
4.93/5 (24 votes)
12 Aug 200516 min read 124.4K   41   21
Rhino Mocks 2.2

Rhino Mocks Logo

Rhino Mocks Version 2.2

Project page.

Download Rhino Mocks

<menu>
  • What is new?

  • The Elevator Speech

  • Rhino Mocks capabilities

  • That is enough talking, let's see some code

  • Ordered / Unordered

  • The IDisposable pattern

  • Method Calls

  • Properties

  •  

    <menu>
  • Callbacks

  • Recursive Expectations

  • Method Options Interface

  • Constraints

  • Setup Result

  • Mocking classes

  • Limitations

  •  

    License: BSD

    Elevator Speech: Top

    Rhino Mocks allows you to easily create mock objects and setup a wide range of expectations on them using strongly typed notation instead of compiler opaque strings. It's as simple as:

    IProjectView projectView = (IProjectView)mocks.CreateMock(typeof(IProjectView));
    
    Expect.Call(projectView.Title).Return("Project's Title");

    Definitions:

    • Mock Object - an object that pretend to be another object, and allows to set expectations on its interactions with another object.

    • Interaction Based Testing - you specify certain sequence of interactions between objects, initiate an action, and then verify that the sequence of interactions happened as you specified it.

    • State Based Testing - you initiate an action, and then check for the expected results (return value, property, created object, etc).

    • Expectation - general name for validation that a particular method call is the expected one.

    • Record & Replay model - a model that allows for recording actions on a mock object, and then replaying and verifying them. All mocking frameworks uses this model. Some (NMock, TypeMock.Net, NMock2) uses it implicitly and some (EasyMock.Net, Rhino Mocks) uses it explicitly.

    • Ordering - The ability to specify that replaying a sequence of method calls will occur in a specific order (or disorder).

    What is new? Top

    Version 2.2:

    • Explicit syntax for specifying method's options.

    • Many bug fixes.

    Version 2.0:

    • Explicit record & replay model.

    • Explicit LastCall model.

    • Single repository for all mocks.

    • Ability to specify an ordering for the methods under test.

    • IDisposable implementation to make it easier to verify.

    • Flexible constraints.

    • Method callbacks.

    • Easy extending of the framework.

    • Early failing on errors.

    • Mock classes as well as interfaces, and make sure that both behave exactly like they are supposed to (and not sort of like it.)

     

    Rhino Mocks capabilities: Top

    • mock interface and concrete classes, including those with parameterized constructors.

    • set expectations on the called methods by using strongly typed mocks instead of strings.

    • lends itself easily for refactoring & leaning on the compiler.

    • allows wide range of expectations to be set on a mock object, or several mock objects.

    Let's get down to the code, okay? [I tried to create an example for this article, but the example was both contrived and didn't portrayed the possibilities correctly. So most of the code samples here were taken from NHibernate Query Analyzer tests and are "real code".]

    The purpose of mock objects is to allow you to test the interactions between components, this is very useful when you test code that doesn't lend itself easily to state base testing. Most of the examples here are tests to check that the save routine for a view is working as expected. The requirements for this method are:

    • If the project is a new project, then ask for a name for the project. [ Allow canceling save at this point ]

      • If the new project name already exists, ask if user want to overwrite it. [ If no, cancel the save operation ]

      • If the user approve overwrite, delete old project.

    • Save project.

    So we have five tests here:

    1. Project is new project, and the user cancels on new name.

    2. Project is new project, the user gives a new name, and the project is saved.

    3. Project is new project, the user gives a name that already exists and don't approve overwrite.

    4. Project is new project, the user gives a name that already exist and approve overwrite.

    5. Project already exists, so just save it.

    Trying to test that using state based testing is going to be both awkward and painful, using interaction based testing, it would be a breeze (other types of tests would be just as painful to test using interaction testing but easy using state testing.)

    That is enough talking, let's see some code: Top

    C#
    [Test]<br/>public void SaveProjectAs_CanBeCanceled()<br/>{<br/>    using(MockRepository mocks = new MocksRepository())<br/>    {<br/>        IProjectView projectView = (IProjectView)projectView.CreateMock(typeof(IProjectView));<br/>        Project prj = new Project("Example Project");<br/>        IProjectPresenter presenter = new ProjectPresenter(prj,projectView);<br/>        Expect.Call(projectView.Title).Return(prj.Name);<br/>        Expect.Call(projectView.Ask(question,answer)).Return(null);<br/>        mocks.ReplayAll();<br/>        Assert.IsFalse(presenter.SaveProjectAs());            <br/>    }<br/>}<br/>

    We create a MockRepository and create a mock project view, project and a project presenter, then we set the expectations. After we finished with setting up the expectations, we move to the replay state, and call the SaveProjectAs() method, which should return false if the user canceled the save process.

    This example clarify several key concepts with Rhino Mocks.

    • We set expectation on object using the object's methods, and not strings, this reduce the chances of creating a mistake that will only (hopefully) be caught at runtime, when you run the tests.

    • We are using explicit call to move from Record state to Replay state.

    • MockRepository implement IDisposable; so when we reach the end for the using statement it's verifying that all the expectations has been met.

    This is about as simple example as can be had, the real test moves creating the MockRepository, the project, the view and presenter to the setup method, since they are require for each test. You can see that we expected two methods to be called, with specific arguments, and that we set a result for each. This method uses parameter  matching expectations, Rhino Mocks supports several more. More info: Method Calls.

    Ordered / Unordered: Top

    Method calls in Rhino Mocks can be ordered or unordered. The default state for a recorder is unordered recording, this means that during replay, methods can come at any order. If the recorder is changed to ordered, then the methods must be called in the exact same order as they were recorded. Here is a code sample:

    C#
    [Test]<br/>public void SaveProjectAs_NewNameWithoutConflicts()<br/>{<br/>    using(mocks.Ordered())<br/>    {<br/>        Expect.Call(projectView.Title).<br/>            Return(prj.Name);<br/>        Expect.Call(projectView.Ask(question,answer)).<br/>            Return( newProjectName);<br/>        Expect.Call(repository.GetProjectByName(newProjectName)).<br/>            Return(null);<br/>        projectView.Title = newProjectName;<br/>        projectView.HasChanges = false;<br/>        repository.SaveProject(prj);<br/>    }<br/>    mocks.ReplayAll();<br/>    Assert.IsTrue(presenter.SaveProjectAs());<br/>    Assert.AreEqual(newProjectName,prj.Name);<br/>}<br/>

    In the above code example we ask Rhino Mocks to verify that the calls come in the exact same order. Notice that we specify expectations on several mock objects, and Rhino Mocks will handle the ordering between them. This mean that if I set the project view title before I get a project from the repository, the test will fail. In Rhino Mocks, the default is to use unordered matching, but it support unlimited depth of nesting between ordered and unordered, this means that you can create really powerful expectations. Here is a somewhat contrived example:

    C#
    [Test]<br/>public void MovingFundsUsingTransactions()<br/>{<br/>    using(MockRepository mocks = new MockRepository())<br/>    {<br/>        IDatabaseManager databaseManager = (IDatabaseManager)mocks.CreateMock(typeof(IDatabaseManager));<br/>        IBankAccount accountOne = (IBackAccount)mocks.CreateMock(typeof(IBackAccount)),<br/>            accountTwo = (IBackAccount)mocks.CreateMock(typeof(IBankAccount)); <br/>        using(mocks.Ordered())<br/>        {<br/>            Expect.Call(databaseManager.BeginTransaction()).<br/>                Return(databaseManager);<br/>            using(mocks.Unordered())<br/>            {<br/>                accountOne.Withdraw(1000);<br/>                accountTwo.Deposit(1000);<br/>            }<br/>            databaseManager.Dispose();<br/>        }<br/>        mocks.ReplayAll();<br/>    <br/>        Bank bank = new Bank(databaseManager);<br/>        bank.TransferFunds(accountOne,accountTwo,1000);<br/>    }<br/>}

    This code verify that the transfer of funds from one account to another is wrapped in a transaction, but the implementation is free to withdraw from the first account first, or to deposit into the second account first, both are legal, as long as both actions happens. The reverse is true as well, you may specified unordered sequence of ordered events (I want A then B then C to happen, and I want D then E then F to happen, but I don't care which sequence comes first).

    Ordering have two caveats:

    • To exit an ordering in replay state, you must call all the recorded methods. In  the above example, we can move from the inner Unordered ordering (the one containing the withdraw and deposit) only after both methods were called. This fall in line with the way the recording code looks, so it shouldn't cause any surprises.

    • You must exit all ordering before you can start replaying. This is enforced by the framework (you get an exception if you try).

    The IDisposable pattern:Top

    The using(...) syntax & IDisposable are very convenient when it comes to make sure you're not forgetting to clean up resources, in Rhino Mocks, it's used in two places:

    • MockRepository implement IDisposable so you won't forget to call VerifyAll(), or call ReplayAll().

      • If an exception has been raised during the test because of Rhino Mocks, the repository won't validate, and you'll get the exception that was thrown.

      • But if an exception was thrown that didn't originate in Rhino Mocks (from an Assert that failed, for instance), then the repository will attempt to verify that all expectations has been met, and will (probably) fail. This result in an exception that masks the real cause for failure. Keep this in mind if you have a failing test. You may want to unroll the using statement in this case, and only verify if no exception has been thrown.

      • The recommended approach is to create the repository in the Setup method, and verify it in the Teardown method, this way you'll get the correct exception. Just call VerifyAll() (or Dispose(), which does the same thing) in your teardown.

    • Ordered() and Unordered() methods on MockRepository, these two methods set the MockRepository to expect the following method in ordered or unordered way. Those calls should be wrapped in a using declaration (or a manual Dispose() call). You cannot start replaying with an open ordering call.

    Method Calls: Top

    When Rhino Mocks intercept a call from a mocked object it determines if the call is expected by checking the method signature, the object this method was called on, whatever the expectation on this method match the arguments passed for the method. The matching of the mocked object and the method signature is not very interesting, but the expectations on a method call is. Rhino Mocks support the following expectations:

    • Matching Parameters - The same arguments that were passed during record state must be passed during the replay state in order to get a match. One caveat, though, for arrays, the comparison is done for the objects contained in the array. This is the default behavior of Rhino Mocks, and require no action on your part.

    • Ignore Parameters - Expect just the method call on this object, any arguments will be accepted. This is useful if you don't care about the arguments. To activate this behavior, you need to call IgnoreArguments() on the IMethodOptions interface (More info: Method Options Interface).

    • Constraints Matching - Each argument is validated against a constraint, to accept the call, all constraints must pass. (More info: Constraints.)

    • Callbacks - A user supplied delegate is called to check if a method call is valid, if the delegate return true, then the method is accepted. (More info: Callbacks.)

    Properties: Top

    If you want to mock properties, you need to keep in mind that properties are merely special syntax for normal methods, a get property will translate directly to propertyType get_PropertyName() and a set property will translate directory to void set_PropertyName(propertyType value). So how do you create expectations for a property? Exactly as you would for those methods.

    Here is how you set the return value for a get property:

    IList list = (IList)mocks.CreateType(typeof(IList));<br/>Expect.Call(list.Count).Return(42);

    And here is how you would set an expectation for a set property:

    IList list = (IList)mocks.CreateType(typeof(IList));<br/>list.Capacity = 500;//Will create an expectation for this call
    LastCall.IgnoreArguments();//Ignore the amounts that is passed.

    Callbacks: Top

    A callback is a user supplied delegate that is called whenever Rhino Mocks needs to evaluate whatever a method call is expected or not. This is useful in some scenarios when you want to do a complex validation on a method arguments, or have a complex interactions between objects that you need to mock in the tests. I added this feature because I want to test some threading code which had the semantics of: Start Job, and notify me when you're done. The only way to re-create that without bringing the real thread (and kill tests isolations) was to plug my own code during the replay state and call the tested object myself.

    Some things to consider before you decide to use callbacks:

    • It can be abused very easily. It can be very hard to understand tests that use callbacks, because you get calls from supposedly innocent code. Do it only if you need it.

    • You callback may (and likely will be) called several times; keep that in mind when you write it. Either wrap it all in a if ( firstTimeCalled ) { /*do work*/)  or make sure that repeated call for the delegate won't have some nasty side effects.

    If it so open to abuse, why add it?

    • Because when you need it, you really need it, and I would prefer that I'd some nice way to do it, and not some really ugly hacks.

    • Because I respect those who will use this framework not to take the power and abuse it.

    The technical details - In order to be a valid callback, the callback must return a Boolean, and have the same arguments as the mocked methods. You register a delegate using the following code:

    IProjectRepository repository = (IProjectRepository) mocks.CreateMock(typeof(IProjectRepository));<br/>IProjectView view = (IProjectView )mocks.CreateMock(typeof(IProjectView ));<br/>Expect.Call(view.Ask(null,null)).Callback(new AskDelegate(DemoAskDelegateMethod)).Return(null);

    Notice that you cannot change the return value for the method, but must pass it explicitly.

    Recursive Expectations: Top

    One final word of warning regarding callbacks. If your callback will initiate an action that cause another call on a mocked object, this will fail when mixed with Ordered(). The reason for that is that the framework cannot decide whatever to accept the call or not, and calling another mocked method while evaluating means that the currently expected call is the one still being evaluated. This will fail the test. Using Unordered(), it will work, since the second expectation is not dependant on the first one being accepted first. In short, Ordered() is not re-entrant :-)

    Method Options Interface: Top

    The IMethodOptions allows you to set various options on a method call. Here is an example of telling Rhino Mocks to ignore the arguments of a method:

    IProjectRepository repository = (IProjectRepository)mocks.CreateMock(typeof(IProjectRepository));<br/>IProjectView view = (IProjectView ) mocks.CreateMock(typeof(IProjectView ));<br/>Expect.Call(view.Ask(null,null)).IgnoreArguments().Return(null);<br/>repository.SaveProject(null);<br/>LastCall.IgnoreArguments();

    As you can see, we use the Expect.Call() for methods that has return values, and LastCall for methods that return void to get the IMethodOptions interface. I find the Expect.Call() syntax a bit clearer, but there is no practical difference between the two. I would recommend using Expect wherever possible (anything that return a value). For properties setters, or methods returning void, the Expect syntax is not applicable, since there is no return value. Thus, the need for the LastCall. The idea of Last Call is pervasive in the record state, you can only set the method options for the last call  - even Expect.Call() syntax is merely a wrapper around LastCall.

    Expect.Call()  & LastCall allows you to set the following options:

    • The return value of a method, if it has one.

    Expect.Call(view.Ask(null,null)).<br/>    Return(null);
    • The exception the method will throw:

    Expect.Call(view.Ask(null,null)).<br/>    Throw(new Exception("Demo"));
    • The number of times this method is expected to repeat (there are a number of convenience methods there):

    Expect.Call(view.Ask(null,null)).Return(null).<br/>    Repeat.Twice();
    • To ignore the method arguments:

    Expect.Call(view.Ask(null,null)).Return(null).<br/>    IgnoreArguments();
    • To set the constraints of the method:

    Expect.Call(view.Ask(null,null)).Return(null)<br/>    .Constraints(<br/>        Text.StartsWith("Some"),<br/>        Text.EndsWith("Text"));
    • To set the callback for this method:

    Expect.Call(view.Ask(null,null)).Return(null).<br/>    Callback(new AskDelegate(VerifyAskArguments));

    Note: For methods that return a value, you must specify either a return value or an exception to throw. You will not be able to continue recording or move to replay state otherwise.

    Note II: Method chaining really makes writing this cod easier.

    Constraints: Top

    Constraints are a way to verify that a method arguments match a certain criteria. Rhino Mocks include a number of built in constraints, and allows you to define you own custom one, which will integrate cleanly into the framework. You specify constraints on method arguments using the following syntax:

    Expect.Call(view.Ask(null,null)).Return(null).<br/>    Constraints( <br/>        Text.StartsWith("Some"),<br/>        Text.EndsWith("Text"));

    You need to pass the exact same number of constraints as the number of arguments of the method. When a method is called during replay state, Rhino Mocks evaluate each constraint against the parameter in the same index, and accept the method if all the constraints were met. As a note, I got the idea of the current constraints syntax from NMock2, it is much leaner approach to create constraints.

    Rhino Mocks built in constraints:

    Rhino mocks ships with the following constraints:

    Constraint:

    Example:

    Accepted Value(s):

    Unaccepted Value(s):

    Object

    Anything

    Is.Anything()

    Anything at all {0,"","whatever",null, etc}

    (Nothing at all!)

    Equal

    Is.Equal(3)

    (int)3

    3f, 4,"", new object()

    Not Equal

    Is.NotEqual(3) or !Is.Equal(3)

    3f,43,null,DateTime.Now

    (int)3

    Null

    Is.Null()

    null

    5,"",new object()

    Not Null

    Is.NotNull()

    DateTime.Now, "asbcd", 0xHead

    null

    Type Of

    Is.TypeOf(typeof(string))

    "","Hello",String.Empty

    null, 3, DateTime.Now

    Greater Than

    Is.GreaterThan(10)

    15,100,300

    3,4,5, 10

    Greater Than Or Equal

    Is.GreaterThanOrEqual(10)

    10,15,100

    4,2,8,9

    Less Than

    Is.LessThan(10)

    1,2,9

    10,32,100

    Less Than Or Eqaul

    Is.LessThanOrEqual(10)

    1,9,10

    11,33,43

    Property

    Equal To Value

    Property.Value("Length",0)

    new ArrayList(),String.Empty

    "Hello", 5

    Null

    Property.IsNull("InnerException")

    new Exception("exception without inner exception")

    new Exception("Exception with inner Exception", new Exception("Inner")

    Not Null

    Property.IsNotNull("InnerException")

    new Exception("Exception with inner Exception", new Exception("Inner")

    new Exception("exception without inner exception")

    List

    Is In List
    [the parameter is a collection that contains this value]

    List.IsIn(4)

    new int[]{1,2,3,4}, new int[]{4,5,6}

    new object[]{"",3}

    One Of
    [parameter equal to one of the objects in this list]

    List.OneOf(new int[]{3,4,5})

    3,4,5

    9,1,""

    Equal

    List.Equal(new int[]{4,5,6})

    new int[]{4,5,6}, new object[]{4,5,6}

    new int[]{4,5,6,7}

    Text

    Starts With

    Text.StartsWith("Hello")

    "Hello, World", "Hello, Rhino Mocks"

    "", "Bye, Bye"

    Ends With

    Text.EndsWith("World")

    "World","Champion Of The World"

    "world", "World Seria"

    Contains

    Text.Contains("or")

    "The Horror Movie...", "Either that or this"

    "Movie Of The Year"

    Like
    [perform regular expression validation]

    Text.Like("Rhino|rhino|Rhinoceros|rhinoceros" )

    "Rhino Mocks", "Red Rhinoceros"

    "Hello world", "Foo bar", Another boring example string"

    Operator Overloading

    And - operator &

    Text.StartsWith("Hello") & Text.Contains("bar")

    "Hello, Foobar"

    "Hello, World", "Foo bar"

    Or - operator |

    Text.StartsWith("Hello") & Text.Contains("bar")

    "Hello, Foobar", "Hello, World", "Foo bar"

    "boring string"

    Not - operator !

    !Text.StartsWith("Hello")

    "Foo bar", "index.html"

    "Hello, Rhino Mocks"

    Operator Overloading warning: Pay attention to operator precedence if you are using several operators in a single statement, otherwise you may get unexpected results.

    Creating custom constraints: Creating you custom constraint is easy, just derive a class from AbstractConstraint and return true from the Eval() method.

    Setup Result: Top

    Sometimes you have a method on your mocked object where you don't care how / if it was called, you may want to set a return value (or an exception to be thrown), but for this specific test, you just don't care. For example, you may have some interaction that you already verified, or you are testing some other class and just need to get the right value from a method. The way to do it in Rhino Mocks is to use SetupResult.For(). Here is the code:

    [Test]<br/>public void SetupResultUsingOrdered()<br/>{<br/>    SetupResult.For(demo.Prop).Return("Ayende");<br/>    using(mocks.Ordered())    {<br/>        demo.VoidNoArgs();<br/>        LastCall.On(demo).Repeat.Twice();<br/>    }<br/>    mocks.ReplayAll();<br/>    demo.VoidNoArgs();<br/>    for (int i = 0; i < 30; i++)<br/>    {<br/>        Assert.AreEqual("Ayende",demo.Prop);                <br/>    }<br/>    demo.VoidNoArgs();<br/>}

    When we use SetupResult.For() we tell Rhino Mocks: "I don't care about this method, it need to do X, so just do it, but otherwise ignore it." Using SetupResult.For() completely bypass the expectations model in Rhino Mocks. In the above example, we define demo.Prop to return "Ayende", and we can all it no matter in what the currently expected method is.

    What about methods returning void?
    You would use LastCall.Repeat.Any(), which has identical semantics (ignore ordering, etc).

    Note: If you want to have a method that can repeat any number of time, but still obey ordering, you can use: LastCall.On().Repeat.Times(0,int.MaxValue), this does obey all the normal rules.

    Mocking classes: Top

    Rhino Mocks supports mocking concrete classes as well as interfaces. In fact, it can even mock classes that doesn't have a default constructor! To mock a class, simply pass its type to MockRepository.CreateMock() along with any parameters for the constructor.

    [Test]<br/>public void AbuseArrayList()<br/>{<br/>    using(MockRepository mocks = new MockRepository())<br/>    {
            ArrayList list = (ArrayList)mocks.CreateMock(typeof(ArrayList));<br/>        Expect.Call(list.Capacity).Return(999);<br/>        mocks.ReplayAll();<br/>        Assert.AreEqual(999,list.Capacity);r/>    }<br/>}

    If you want to call the non default constructor, just add the parameters after the type. Like this:

    ArrayList list = (ArrayList)mocks.CreateMock(typeof(ArrayList),500);

    Some things to be aware of:

    • You cannot create a mock object from a sealed class.

    • You cannot intercept calls to non-virtual methods.

    Limitations: Top

    • Currently Rhino Mocks can't mock interfaces and classes that use out or ref parameters.

    License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here


    Written By
    Israel Israel
    This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

    Comments and Discussions

     
    GeneralException message Pin
    Scott Langham21-Sep-07 5:02
    Scott Langham21-Sep-07 5:02 
    GeneralRe: Exception message Pin
    Ayende @ Rahien21-Sep-07 5:41
    Ayende @ Rahien21-Sep-07 5:41 
    QuestionMailing list? Pin
    Artem Smirnov15-Aug-07 2:13
    professionalArtem Smirnov15-Aug-07 2:13 
    QuestionHow to fire events on mocked interfaces? Pin
    Drew Noakes6-Apr-07 1:09
    Drew Noakes6-Apr-07 1:09 
    AnswerRe: How to fire events on mocked interfaces? Pin
    Ayende @ Rahien6-Apr-07 1:21
    Ayende @ Rahien6-Apr-07 1:21 
    GeneralNew version available Pin
    Drew Noakes6-Apr-07 0:12
    Drew Noakes6-Apr-07 0:12 
    GeneralRe: New version available Pin
    Ayende @ Rahien6-Apr-07 0:15
    Ayende @ Rahien6-Apr-07 0:15 
    GeneralRe: New version available Pin
    Drew Noakes6-Apr-07 0:54
    Drew Noakes6-Apr-07 0:54 
    GeneralRe: New version available Pin
    Judah Gabriel Himango18-Apr-07 10:23
    sponsorJudah Gabriel Himango18-Apr-07 10:23 
    GeneralRe: New version available Pin
    Ayende @ Rahien18-Apr-07 10:29
    Ayende @ Rahien18-Apr-07 10:29 
    GeneralRe: New version available Pin
    Judah Gabriel Himango18-Apr-07 10:49
    sponsorJudah Gabriel Himango18-Apr-07 10:49 
    GeneralOrdered Expectations Pin
    sosoqool13-Dec-06 22:32
    sosoqool13-Dec-06 22:32 
    GeneralRe: Ordered Expectations Pin
    sosoqool13-Dec-06 22:39
    sosoqool13-Dec-06 22:39 
    GeneralReturning Objects Pin
    Burt26-May-06 5:56
    Burt26-May-06 5:56 
    GeneralRe: Returning Objects Pin
    Ayende @ Rahien26-May-06 9:35
    Ayende @ Rahien26-May-06 9:35 
    Questionout & ref Pin
    Nicolas M. Diaz22-Sep-05 10:15
    Nicolas M. Diaz22-Sep-05 10:15 
    AnswerRe: out &amp; ref Pin
    Ayende @ Rahien22-Sep-05 12:40
    Ayende @ Rahien22-Sep-05 12:40 
    AnswerRe: out & ref Pin
    Ayende @ Rahien26-May-06 9:35
    Ayende @ Rahien26-May-06 9:35 
    GeneralRe: out & ref Pin
    Riz Thon31-Aug-06 6:11
    Riz Thon31-Aug-06 6:11 
    QuestionDumb question, when to use? Pin
    Hardy Wang22-Sep-05 4:40
    Hardy Wang22-Sep-05 4:40 
    AnswerRe: Dumb question, when to use? Pin
    Ayende @ Rahien22-Sep-05 7:57
    Ayende @ Rahien22-Sep-05 7:57 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.