On my current contract the powers that be feel that datasets are the way to go for everything and it has become their new standard. While I feel that datasets are powerful and useful in some (rare) situations I feel that this is a bad idea. I did not find much on the comparison between the two techniques so I thought I would write one up. Please don't take this as an attack on datasets typed or untyped). I am just pointing out why I avoid them for most apps. I appreciate any contributions to this post either for or against datasets.

Terminology
Dataset: (ok everyone reading should know this)
TypedDataset: An object that is generated from an xmlschema that has all the functionality of a dataset plus gives you strong typing and intellisense when using it (always try to use a typed dataset if using dataset at all as it saves time and you do not have the string for column names scattered everywhere)
DTO (Data Transfer Object): A custom object that is used to transfer information i.e.:
public class Person
{
     private string firstName;
     private string lastName;

     public string FirstName
     {
         get{ return firstName; }
         set { firstName = value; }
     }
    
    ...property implementation of lastName
}

Re-useability of Parts
If two datasets needed an address table it would have to be re-created in both datasets (i.e. if the person had an address table and company had an address table then you would have to duplicate your effort by creating two identical address tables). By using a DTO you could create one address object and re-use it where needed.

Lack of Control for Messages
By this I mean that you can not control how many records get passed between methods. For instance if I have this method:
public void UpdatePerson(Datasets.Person person)

A developer might get all people, iterate through the dataset to find the right person, change that person, and then send the whole dataset back to update one person. A more experienced developer might do a dataset.getchanges() to only send back the modified records (or call a get method that only retrieves the person they are looking for if it exists). The problem with this is that it is now up to others to make sure that they don't bombard you with data and in the real world people don't usually think about the size of messages being sent/received until it becomes a performance issue.

By having a concise method signature using a DTO it makes in impossible for a consumer of the method to send more than one record:
public void UpdatePerson(Entities.Person person)

And if the method was supposed to take multiple records:
public void UpdatePeople(IList<Entities.Person> person)

Mindset
So far I have found that when people use datasets for their transfer objects that the dataset is a mirror of their database and the application turns into one big dataset validator. This is not always the case of course but I find that due to datasets behaving like a database that developers simply copy the database structure. Usually with creating a DTO developers seem to develop more towards the domain objects and map those objects to the database where appropriate. I find that doing DTOs makes me think a bit more about what I am doing and the best way to represent objects in the system instead of representing the data in the system

Extendability
Datasets have a lot of built in functionality already which can be a blessing and a curse. To add functionality to a dataset you can use partial classes in .NET 2.0 and up. While this is a perfectly valid technique it makes me cringe. The reason for that is that to add functionality onto a dataset you are creating new files and then to support that feature you are now hunting around for those partial class files (granted a good file organization structure can help with this but Jr. developers seem to never want to create folders in solutions).

To extend a DTO... simply add the functionality. It's in the same file, it's simple, and it is fast.

Clarity
My biggest issue with using a dataset is that it exposes things that users of the object will rarely or never need:

Properties Methods
CaseSensitive
Container
DataSetName
DefaultViewManager
DesignMode
EnforceConstraints
ExtendedProperties
HasErrors
IsInitialized
Locale
Namespace
Prefix
Relations
RemotingFormat
SchemaSerializationMode
Site
Tables
AcceptChanges
BeginInit
Clear
Clone
Copy

CreateDataReader
Dispose
EndInit
Equals
GetChanges
GetDataSetSchema
GetHashCode
GetObjectData
GetService
GetType
GetXml
GetXmlSchema
HasChanges
InferXmlSchema
Load
Merge

ReadXml
ReadXmlSchema
ReferenceEquals
RejectChanges
Reset
ToString
WriteXml
WriteXmlSchema
All the items I have ever used are bolded

And that is just the dataset object! The same concept applies to datasets. As you can see this exposes way more than is required to consumers of the object. With a DTO we can add only the methods that are required and keep the object much easier to use.

On the topic of easy to use which is easier:
Datasets.PersonDataset ds = new Datasets.PersonDatasset
Datasets.PErsonDataset.PersonRow row
row = ds.People.NewPeople()
row.FirstName = "Alex"
row.LastName = "Kieth"
ds.People.AddPeople(row)

Or:
Entities.Person person = new Entities.Person()
person.FirstName = "Alex"
person.LastName = "Alex Kieth"

Required Values
It is a pain to force the creation of a datarow with certain values. A factory method is probably the way to go for this but a consumer has the ability to just use the raw datarow still. With a DTO we can easily force required values via the constructor:
Entities.Person person = new Entities.Person("FirstName", "LastName")

For a datarow we would have to write code to validate that the proper information was present and if not return an error back to the consumer (or throw an exception). This validation needs to now be in place where ever the datarow can be used.

Creation Time
This is where I feel that typed datasets win. They are fast and easy to create. A DTO takes time to create the fields and properties.... unless you are using a refactoring tool like Resharper and then I would say that the times are comparable for creation of the object.

Testing
A DTO is easy to drive out when using TDD and Resharper whereas with using a dataset there is a real time hit switching back and forth from your dataset designer. Datasets are not easy to mock out for those that use mocks.

Abstraction
I have never tried to create an interface for a typed dataset but I am sure I would not want to. If I wanted to create a proxy to a DTO is would be as simple as implementing the same interface on both the real and proxy classes. To do this for a datarow..... I would not want to think of what is required to do this.

Debugging
Datasets lead to data adapters and data adapters lead to drinking. There I said it. While the dataadapter and dataset work really well together it is impossible to debug (I usually end up profiling SQL to see what is actually happening to debug any issues at this point). While using a DTO is usually more code to map to and from your database (unless you are using an ORM), it is much easier to debug and walk through.

Performance
Apparently in .NET 2.0 the performance of serializing and deserializing a dataset is in binary and much faster and smaller than the old xml way. That being said you are still serializing a lot of extra objects and properties that probably are not required. A DTO only has what is required in it so serialization should always be faster and smaller when using a DTO. It is also easier to customize your serialization for a DTO

Conclusion
As much as I tried to keep this balanced.... its not. DTOs have so many advantages compared to the dataset that I can not fathom using a dataset for a transfer object. The only times would be when I needed an object format that could tell the state of records or when I was building a really simple app that would not change (and we know that there are no apps that don't change). The DTOs big disadvantage is creation time but most refactoring / codegen tools will easy that pain away. All in all I would probably use a DTO in 95% of cases and in the rare case use a typed dataset or a plain dataset.