To understand when to use one, you need to know what's going on with the memory behind object and how they get passed to/from functions. Basically there are two types of variables in .NET: value type and reference type. Base types (int, char, decimal, etc.) and structs are value types, classes are reference types.
When you pass a reference type ByVal to a function, you are only passing a copy of the *pointer*, not the data inside. That means that if you change the data inside the function, it affects the object itself and when you return those changes can be seen. What you CAN'T do when passing a reference type as ByVal is change the pointer inside the function. So if you had a variable ds in Function1 that pointed to a DataSet and you pass it ByVal to Function2 and Function2 sets it to Nothing, ds will still point to the DataSet. If you were to remove a table inside Function2, it would be removed for good and you won't see it when you return to Function1.
Structs work differently. If you had a variable p that was type Point (in System.Drawing I think) and pass it ByVal to a function, it creates a copy of whatever data is in p and that's what you're working on inside Function2. If you change a value in Function2, it won't affect the variable p's data back in Function1. Now if you pass p ByRef, any change you make in Function2 will show up back in Function1.
Hope that helps,
ner