Friday, June 20, 2014

Мифы в C#: По умолчанию объекты C# передаются по ссылке

В этой статье мы развеим один популярный миф, который бытует среди многих разработчиков языка C#. Мы рассмотрим, почему это миф, используя для этой цели книгу Jon Skeet, "C# in Depth, 3rd Edition", MSDN "Passing Parameters (C# Programming Guide)", и поскольку для таких целей лучше всего доказывать на практике, мы будем использовать для примеров язык C#, а также смотреть в IL код. В своей книге Скит называет этот миф так "Objects are passed by reference in C# by default". 
Небольшое отступление, почему я решил затронуть эту тему. Не так давно, во время прохождения собеседований на должность Software .NET Developer, в двух компаниях мне был задан вопрос, который звучал приблизительно одинаково: "Как передаются аргументы в функцию в C#?". В первой компании с моим ответом согласились после того, как запустили пример, который я набросал. На втором же собеседовании меня пытались убедить в том, что я неправильно понимаю происходящее, и что ссылочные типы передаются по умолчанию по ссылке. Ладно, что, по сути, эти ребята подменяют понятия, которые присутствуют в языке C# и описаны не только в MSDN, но и легендарным Скитом, который, своего рода, Чак Норрис в мире C#. Меня больше поразил тот факт, что эти специалисты аргументировали свои доводы, проецируя передачу аргументов в языке C# на передачу тех же аргументов в языке С++, в котором они затронули тему указателей и ссылок. Для профессиональных разработчиков пытаться накладывать возможности одного языка на другой и возмущаться по поводу отсутствия в другом языке тех или иных возможностей, по крайней мере, странно. Сложно понять специалистов, которые в качестве аргументации передачи объектов в функцию по умолчанию приводят пример с использованием в С++ указателей. 
Давайте начнем с того, что в C# нет указателей в чистом виде. Получить указатель на объект можно, но с помощью разных хаков, которые можно посмотреть в статье "Получение указателя на объект .Net". В C# есть возможность работать с указателями через unsafe, но для этого, если мы работаем через студию, нам нужно внести некоторые настройки в сборку проекта.
Но это все равно не то же, что имелось в виду. Давайте посмотрим, что об этом говорит Скит. Я  процитирую переведенный вариант с второго издания его книги.
Это, вероятно, наиболее широко распространенный миф. Люди, которые утверждают это, зачастую (хоть и не всегда) знают, как фактически ведет себя язык C#, но не знают, что действительно означает термин “передача по ссылке” (pass by reference). К сожалению, это сомнительно для людей, которые действительно знают, что это означает. Формальное определение передачи по ссылке относительно сложно, оно подразумевает такую терминологию, как l-значение (l-value) и подобное, но важно то, что если вы передаете переменную по ссылке, то вызванный метод может изменить значение переменной вызывающей стороны при изменении значения ее параметра. Теперь вспомните, что содержимым переменной ссылочного типа является ссылка, а не сам объект непосредственно. Вы можете изменить содержимое объекта, на который указывает параметр, без передачи самого параметра по ссылке. Например, следующий метод изменяет содержимое объекта StringBuilder, но выражение вызывающей стороны все еще остается тем же объектом, что и прежде.
void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}
При вызове этого метода значение параметра (ссылка на объект StringBuilder) передается по значению. Например, если я должен изменить в методе значение переменной builder при помощи оператора builder = null, то это изменение, вопреки мифу, не будет замечено вызывающей стороной.
Интересно заметить, что не только часть “по ссылке” выражения является мифом, но и часть “объекты передаются”. Сами объекты никуда не передаются, ни по ссылке, ни по значению. Когда используется ссылочный тип, либо переменная передается по ссылке, либо значение аргумента (ссылка) передается по значению. Кроме всего прочего, это отвечает на вопрос о том, что происходит, когда в качестве аргумента используется значение null, ‒ если бы передавались объекты, возникла бы проблема, поскольку не будет объекта для передачи! Вместо этого ссылка null передается как значение, точно так же как любая другая ссылка.
Если это краткое объяснение оставило у вас вопросы, вы можете обратиться к статье на моем веб‒сайте C# (http://mng.bz/otVt), где тема раскрыта более подробно. Эти мифы не единственные. Упаковка и распаковка вносят свою долю недоразумений, которые я пытаюсь устранить.
Давайте проверим утверждение Скита на примере. Для этого создадим класс SomeClass и добавим в него одну переменную типа string x.
class Program
{
    static void Main(string[] args)
    {
        var someClass = new SomeClass();
        someClass.x = "hello";
        ChangeSomeChass(someClass);
        Console.WriteLine(someClass.x);
    }

    public static void ChangeSomeChass(SomeClass s)
    {
        s = null;
    }
}

public class SomeClass
{
    public string x;
}
Как вы думаете, что будет выведено на экране? Если вы предполагаете, что не будет выведено ничего, то вы ошибаетесь. На экран будет выведено слово "hello".
Вот как будет выглядеть данный код в IL:
.class private auto ansi beforefieldinit ReferenceTypeSample.Program
        extends [mscorlib]System.Object
    {
        // Methods
        .method private hidebysig static
            void Main (
                string[] args
            ) cil managed
        {
            // Method begins at RVA 0x2050
            // Code size 38 (0x26)
            .maxstack 2
            .entrypoint
            .locals init (
                [0] class ReferenceTypeSample.SomeClass someClass
            )

            IL_0000: nop
            IL_0001: newobj instance void ReferenceTypeSample.SomeClass::.ctor()
            IL_0006: stloc.0
            IL_0007: ldloc.0
            IL_0008: ldstr "hello"
            IL_000d: stfld string ReferenceTypeSample.SomeClass::x
            IL_0012: ldloc.0
            IL_0013: call void ReferenceTypeSample.Program::ChangeSomeChass(class ReferenceTypeSample.SomeClass)
            IL_0018: nop
            IL_0019: ldloc.0
            IL_001a: ldfld string ReferenceTypeSample.SomeClass::x
            IL_001f: call void [mscorlib]System.Console::WriteLine(string)
            IL_0024: nop
            IL_0025: ret
        } // end of method Program::Main

        .method public hidebysig static
            void ChangeSomeChass (
                class ReferenceTypeSample.SomeClass s
            ) cil managed
        {
            // Method begins at RVA 0x2082
            // Code size 5 (0x5)
            .maxstack 8

            IL_0000: nop
            IL_0001: ldnull
            IL_0002: starg.s s
            IL_0004: ret
        } // end of method Program::ChangeSomeChass

        .method public hidebysig specialname rtspecialname
            instance void .ctor () cil managed
        {
            // Method begins at RVA 0x2088
            // Code size 7 (0x7)
            .maxstack 8

            IL_0000: ldarg.0
            IL_0001: call instance void [mscorlib]System.Object::.ctor()
            IL_0006: ret
        } // end of method Program::.ctor

    } // end of class ReferenceTypeSample.Program
Давайте попробуем передать наше значение по ссылке, указав для это параметр ref.
class Program
{
    static void Main(string[] args)
    {
        var someClass = new SomeClass();
        someClass.x = "hello";
        ChangeSomeChass(ref someClass);
        Console.WriteLine(someClass == null);
    }

    public static void ChangeSomeChass(ref SomeClass s)
    {
        s = null;
    }
}

public class SomeClass
{
    public string x;
}
После запуска программы увидим, что у нас ссылка на someClass обнулилась.
Давайте посмотрим на то, какие произошли отличия в IL коде.
// Methods
        .method private hidebysig static
            void Main (
                string[] args
            ) cil managed
        {
            // Method begins at RVA 0x2050
            // Code size 37 (0x25)
            .maxstack 2
            .entrypoint
            .locals init (
                [0] class ReferenceTypeSample.SomeClass someClass
            )

            IL_0000: nop
            IL_0001: newobj instance void ReferenceTypeSample.SomeClass::.ctor()
            IL_0006: stloc.0
            IL_0007: ldloc.0
            IL_0008: ldstr "hello"
            IL_000d: stfld string ReferenceTypeSample.SomeClass::x
            IL_0012: ldloca.s someClass
            IL_0014: call void ReferenceTypeSample.Program::ChangeSomeChass(class ReferenceTypeSample.SomeClass&)
            IL_0019: nop
            IL_001a: ldloc.0
            IL_001b: ldnull
            IL_001c: ceq
            IL_001e: call void [mscorlib]System.Console::WriteLine(bool)
            IL_0023: nop
            IL_0024: ret
        } // end of method Program::Main

        .method public hidebysig static
            void ChangeSomeChass (
                class ReferenceTypeSample.SomeClass& s
            ) cil managed
        {
            // Method begins at RVA 0x2081
            // Code size 5 (0x5)
            .maxstack 8

            IL_0000: nop
            IL_0001: ldarg.0
            IL_0002: ldnull
            IL_0003: stind.ref
            IL_0004: ret
        } // end of method Program::ChangeSomeChass

        .method public hidebysig specialname rtspecialname
            instance void .ctor () cil managed
        {
            // Method begins at RVA 0x2087
            // Code size 7 (0x7)
            .maxstack 8

            IL_0000: ldarg.0
            IL_0001: call instance void [mscorlib]System.Object::.ctor()
            IL_0006: ret
        } // end of method Program::.ctor

    } // end of class ReferenceTypeSample.Program
Пройдемся по этих изменениях более детально. Изменился способ вызова функции ChangeSomeClass.
IL_0014: call void ReferenceTypeSample.Program::ChangeSomeChass(class ReferenceTypeSample.SomeClass&)
Если мы посмотрим внимательно на код, то можем увидеть в методе знак &, который указывает, что мы передаем ссылку на исходный объект. Вы, возможно, сталкивались с ссылками в языке С++ и видели амперсанд &,  который и обозначал, что мы работаем со ссылками.
Изменения затронули также саму функцию ChangeSomeClass.
.method public hidebysig static
            void ChangeSomeChass (
                class ReferenceTypeSample.SomeClass& s
            ) cil managed
        {
            // Method begins at RVA 0x2081
            // Code size 5 (0x5)
            .maxstack 8

            IL_0000: nop
            IL_0001: ldarg.0
            IL_0002: ldnull
            IL_0003: stind.ref
            IL_0004: ret
        } // end of method Program::ChangeSomeChass
Посмотрите внимательно на параметр, который передается в метод как аргумент. Мы видим уже знакомый нам амперсанд &, который указывает нам на то, что к нам пришла ссылка на сам объект, который мы можем изменить.
Здесь нет никакой мистики. Просто действительно в первом варианте мы передаем, как говорит Скит, by value (по значению) и by reference (по ссылке). В своей статье, которую Скит приводит в разрушение мифа, есть такое выражение:
This difference is absolutely crucial to understanding parameter passing in C#, and is why I believe it is highly confusing to say that objects are passed by reference by default instead of the correct statement that object references are passed by value by default.
Для тех, кто говорит постоянно о С++ и о том, что там нет такой путаницы, соглашусь. Там мы явно указываем, как мы передаем параметр в функцию.
void ChangeData(SomeClass& s)
{

}

void ChangeData1(SomeClass* s)
{

}

void ChangeData2(SomeClass s)
{

}
Но поскольку у нас не С++, нужно привыкать к терминологии, которую нам дает компания Microsoft. В MSDN мы можем найти следующее описание для передачи ссылочных типов по значению Passing Reference-Type Parameters:
"A variable of a reference type does not contain its data directly; it contains a reference to its data. When you pass a reference-type parameter by value, it is possible to change the data pointed to by the reference, such as the value of a class member. However, you cannot change the value of the reference itself; that is, you cannot use the same reference to allocate memory for a new class and have it persist outside the block. To do that, pass the parameter using the ref or out keyword. For simplicity, the following examples use ref".
Давайте рассмотрим также второй нюанс, который описан в MSDN по ссылке выше, когда мы пытаемся создать новый объект и присвоить его исходному.
class Program
{
    static void Main(string[] args)
    {
        int[] arr = { 1, 4, 5 };
        System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

        Change(arr);
        System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
    }

    static void Change(int[] pArray)
    {
        pArray[0] = 888;  // This change affects the original element.
        pArray = new int[5] {-3, -1, -2, -3, -4};   // This change is local.
        System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
    }
}
Почему у нас такое поведение, все карты открывает  все тот же MSDN:
"Because the parameter is a reference to arr, it is possible to change the values of the array elements. However, the attempt to reassign the parameter to a different memory location only works inside the method and does not affect the original variable, arr".
Здесь объясняется, что поскольку параметр — это ссылка на arr, мы можем изменить значения элемента этого массива.  Однако переприсвоение входящего параметра не возымеет никакого эффекта из-за определения в разных областях памяти, поэтому переопределение будет работать внутри метода Change не приведет к изменения оригинальной переменной arr.
In the preceding example, the array, arr, which is a reference type, is passed to the method without the ref parameter. In such a case, a copy of the reference, which points to arr, is passed to the method. The output shows that it is possible for the method to change the contents of an array element, in this case from 1 to 888. However, allocating a new portion of memory by using the new operator inside the Change method makes the variable pArray reference a new array. Thus, any changes after that will not affect the original array, arr, which is created inside Main. In fact, two arrays are created in this example, one inside Main and one inside the Change method.
Основная мысль, которую нам пытается здесь донести MSDN, заключается в том, что мы, по сути, передаем в метод копию ссылки, которая ссылается на массив arr.

Самое главное в данной статье которое я хотел бы донести к читателям, которые плотно работают с языком C#, заключается в том, что ссылочные типы по умолчанию передаются как параметры в методы по значению

Monday, June 16, 2014

Введение в Prism 5. Навигация на основе состояний (State-Based Navigation)

Здравствуйте, уважаемые читатели моего блога. В этой статье мы рассмотрим далее варианты использования библиотеки Prism 5. В этот раз мы уделим внимание навигации, которая базируется на состоянии (VisualState) и имеет название State-Based Navigation. Термин "навигация" определён как процесс, в котором приложение координирует изменения UI в ответ на жесты пользователя или изменения внутреннего состояния приложения.
При навигации на основе состояний, представление обновляется как при изменениях состояния модели представлении, так и при взаимодействии пользователя с самим представлением. При этом, вместо того, чтобы заменять представление другим представлением, просто меняется его состояние. В зависимости от того, как меняется состояние представления, это может восприниматься пользователем как навигация.
Этот стиль навигации хорошо подходит в следующих случаях:
  • Представлению нужно отобразить те же самые данные или функционал, но в другом виде или формате.
  • Представление должно изменить свою разметку, или стиль, в ответ на изменение состояния модели представления
  • Представление должно произвести модальное, или немодальное взаимодействие с пользователем, без смены контекста представления.
Иногда нужно показать одни и те же данные в разных стилях. Примером использования такой анимации может служить тот, который идет в поставке с библиотекой Prism 5 – State-Based Navigation QuickStart.
В данном примере есть анимированный переход, в зависимости от выбранного типа анимации, как показано на рисунке ниже.
Так как в данном примере представление использует одну и туже модель для привязки, но отображает эти данные по-разному, то модель представления, по сути, в данном процессе не участвует как таковая. Она не выполняет каких-либо действий. Вся работа ложится на xaml и на наши знания Expression Blend. В примере, который идет в поставке, используется DataStateBehavior для переключения между визуальными состояниями, определёнными в менеджере визуальных состояний (visual state manager), с помощью радиокнопок. Одна кнопка включает представление контактов в виде списка, другая — в виде аватарок.
<ei:DataStateBehavior Binding="{Binding IsChecked, ElementName=ShowAsListButton}"
                                  Value="True"
                                  TrueState="ShowAsList" FalseState="ShowAsIcons"/>
Когда пользователь нажимает радиокнопку Contacts или Avatar, визуальное состояние переключается между ShowAsList и ShowAsIcons состоянием. Анимация флип-перехода также определена в менеджере визуальных состояний. Для более детального ознакомления с анимацией на основании состояний вы можете ознакомиться на habrahabr в статье "Руководство разработчика Prism – часть 8, навигация", которая описывает возможности Prism 4.1. Но поскольку в данном аспекте ничего не изменилось, по сравнению с Prism 5, для ознакомления этой информации будет достаточно. Либо можете посмотреть в документации к Prism 5 ("8: NavigationUsing the Prism Library 5.0 for WPF") на английском, которая больше затрагивает изменения, которые коснулись второго типа анимации, основанной на изменении модели и которая в Prism называется навигацией на основе представлений (View-Based Navigation). Этот тип навигации мы рассмотрим в следующей статье, поэтому для вас будет достаточно той информации, которая приведена по ссылке с сайта habrahabr.
Теперь второй нюанс, который я хотел бы донести до читателей данной статьи. Поскольку я больше являюсь backend developer, для меня сложно рисовать красивую анимацию в Expression Blend, поэтому анимацию, использованную в статье, я позаимствовал с примера State-Based Navigation QuickStart, который идет в поставке, и адаптировал его под свои нужды. Если вы умеете хорошо использовать VisualStates, Transition и другие возможности анимации на основании состояний, вы сможете более элегантно сделать анимацию под ваши нужды. А пока, особо не заморачиваясь, создадим по старинке новый WPF проект и назовем его StateBasedNavigationSample.
Я для создания использовал .NET Framework 4.5.1, но вы можете выбрать минимальный .NET Framework 4.5. С меньшей версией фреймворка у вас не станет Prism 5. Затем по аналогии, как описано в статье "Введение в Prism 5. Bootstrapper", реализуем начальную структуру проекта. Ниже вы можете увидеть базовую структуру проекта, которая получилась у меня.
Проект построен вокруг классического паттерна MVVM, и с Prism 5 взято самую крохотную часть.
  • Assets – хранится информация о стилях;
  • Models ‒ хранится информация о моделях проекта;
  • ViewModels – здесь мы храним информацию о модели представления;
  • Views – хранится информация о представлениях.
Поскольку я часть логики взял с примеров, которые идут в Prism 5, то для того чтобы не создавать стиль непосредственно в контролах, как это было сделано в примере State-Based Navigation с поставки, я вынес их отдельно в папку Assets. Давайте добавим в папку Assets новый словарь ресурсов Resource Dictionary и назовем его Style.xaml.
Затем в созданный файл ресурсов добавим нужные стили для наших контролов.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Color x:Key="PrimaryColor">#FF63AADA</Color>
    <SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}" />
    <Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        <Setter Property="HorizontalAlignment" Value="Stretch"/>
    </Style>

    <Style x:Key="ContactsList" TargetType="ListBox">
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
                        <TextBlock Text="{Binding Author}" Margin="0,0,10,0"/>
                        <TextBlock Text="{Binding Title}" Margin="0,0,10,0" FontStyle="Italic"/>
                        <TextBlock Text="{Binding Price, StringFormat='{}{0:C}'}" />
                    </StackPanel>
                </DataTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemContainerStyle" Value="{StaticResource ListBoxItemStyle}"/>
    </Style>

    <Style x:Key="AvatarsList" TargetType="ListBox">
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate>
                    <StackPanel Orientation="Vertical" Margin="10">
                        <Image Margin="0,0,10,0" Width="75" Height="75" Source="{Binding AvatarUri}"/>
                        <TextBlock Text="{Binding Author}"/>
                    </StackPanel>
                </DataTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <WrapPanel/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
    </Style>
</ResourceDictionary>
Затем сделаем этот словарь ресурсов доступным всему приложению (поскольку у нас этот ресурс один, такой подход позволительный.) Для этого перейдем в файл App.xaml и немного подправим реализацию.
<Application x:Class="StateBasedNavigationSample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             >
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Assets/Style.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>
Поскольку я по старинке создаю во всех проектах модель данных Book, в которой хранится информация о книгах, я добавил сразу картинки тех книг, которые будут использованы в примере сразу в ресурсы.
Это картинки таких книг, как "C# in Depth", "CLR via C# (Developer Reference)" и "Refactoring: Improving the Design of Existing Code". Все эти картинки взяты с Google и просто добавлены в проект. Вы можете поступить так же или скачать проект, который будет доступен в конце статьи, и посмотреть готовую реализацию. 
Теперь пора вернуться к нашей модели Book, в которую добавилось новое поле AvatarUri для хранения пути к картинке (хотя более верным, на мой взгляд, было бы название Cover, только я решил оставить первоначальный вариант, который я "одолжил" у ребят с patterns & practices). Реализация данной модели приведена ниже.
public class Book : BindableBase
{
    private long _id;
    public long Id
    {
        get { return _id; }
        set
        {
            _id = value;
            OnPropertyChanged(() => Id);
        }
    }

    private string _author;
    public string Author
    {
        get { return _author; }
        set
        {
            _author = value;
            OnPropertyChanged(() => Author);
        }
    }

    private string _avatarsUri;
    public string AvatarUri
    {
        get { return _avatarsUri; }
        set
        {
            _avatarsUri = value;
            OnPropertyChanged(() => AvatarUri);
        }
    }

    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            _title = value;
            OnPropertyChanged(() => Title);
        }
    }

    private DateTime _year;
    public DateTime Year
    {
        get { return _year; }
        set
        {
            _year = value;
            OnPropertyChanged(() => Year);
        }
    }

    private string _sn;
    public string SN
    {
        get { return _sn; }
        set
        {
            _sn = value;
            OnPropertyChanged(() => SN);
        }
    }

    private double _price;
    public double Price
    {
        get { return _price; }
        set
        {
            _price = value;
            OnPropertyChanged(() => Price);
        }
    }
}
Теперь создадим в папке ViewModels модель представления ShellViewModel , которая, по сути, просто создаст коллекцию книг и заполнит ее какими-то данными. Ниже представлена реализация этой модели представления.
public class ShellViewModel
{
    public ShellViewModel()
    {
        Initialize();
    }

    private void Initialize()
    {
        Books = new ObservableCollection<Book>(GenerateBooks());
    }

    #region Public Properties
    public ObservableCollection<Book> Books { get; set; }
    #endregion

    #region Static Methods
    public static IEnumerable<Book> GenerateBooks()
    {
        yield return new Book { Id = 1,
            Author = "Jon Skeet",
            Title = "C# in Depth",
            Price = 22.5,
            SN = "ISBN: 9781617291340",
            Year = new DateTime(2013, 9, 10),
            AvatarUri = @"/StateBasedNavigationSample;component/Assets/csharp_in_deps.jpg"
        };
        yield return new Book { Id = 2,
            Author = "Martin Fowler",
            Title = "Refactoring: Improving the Design of Existing Code",
            Price = 41.52,
            SN = "ISBN-10: 0201485672",
            Year = new DateTime(1999, 7, 8),
            AvatarUri = @"/StateBasedNavigationSample;component/Assets/refectoring.jpg"
        };
        yield return new Book { Id = 3,
            Author = "Jeffrey Richter",
            Title = "CLR via C# (Developer Reference)",
            Price = 35,
            SN = "ISBN-10: 0735667454",
            Year = new DateTime(2012, 12, 4),
            AvatarUri = @"/StateBasedNavigationSample;component/Assets/clr_via_csharp.jpg"
        };
    }
    #endregion
}
Так как мы не обрабатываем хоть какие-то действия пользователя, то у нас модель представления, кроме связывания, не выполняет больше никаких действий. После проделанной работы перейдем в нашу оболочку (класс Shell.xaml) и добавим в нее использование DataStateBehavior. Всякая анимация, переключения между состояниями с помощью VisualStateManager, а также использование Viewport3D для моделирования красивого переключения взято с примера State-Based Navigation, так как я не умею создавать такие сложные и красивые стили в Expression Blend. У меня UI проектирование идет со скрипом, вернее, не само проектирование и композиция, они проблем не составляют, а именно наведение марафета контролам. Реализация оболочки Shell.xaml приведена в примере ниже.
<Window x:Class="StateBasedNavigationSample.Shell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <i:Interaction.Behaviors>
            <ei:DataStateBehavior Binding="{Binding IsChecked, ElementName=ShowAsListButton}"
                                  Value="True"
                                  TrueState="ShowAsList" FalseState="ShowAsIcons"/>    
        </i:Interaction.Behaviors>

        <VisualStateManager.VisualStateGroups>

            <VisualStateGroup x:Name="VisualizationStates">

                <VisualStateGroup.Transitions>

                    <VisualTransition From="ShowAsIcons" To="ShowAsList">
                        <Storyboard SpeedRatio="2">

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinner">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0">
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Visible</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinnerBackground">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0">
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Visible</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>

                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Angle" Storyboard.TargetName="rotate">
                                <EasingDoubleKeyFrame KeyTime="0:0:0" Value="360"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="270"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="90"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
                            </DoubleAnimationUsingKeyFrames>

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Avatars">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0.5" >
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Collapsed</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Contacts">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0.5">
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Visible</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinnerBackground">
                                <DiscreteObjectKeyFrame KeyTime="0:0:1">
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Collapsed</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinner">
                                <DiscreteObjectKeyFrame KeyTime="0:0:1">
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Collapsed</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualTransition>

                    <VisualTransition From="ShowAsList" To="ShowAsIcons">
                        <Storyboard SpeedRatio="2">

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinner">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0">
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Visible</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinnerBackground">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0">
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Visible</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>

                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Angle" Storyboard.TargetName="rotate">
                                <EasingDoubleKeyFrame KeyTime="0:0:0" Value="360"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="270"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="90"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
                            </DoubleAnimationUsingKeyFrames>

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Contacts">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0.5" >
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Collapsed</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Avatars">
                                <DiscreteObjectKeyFrame KeyTime="0:0:0.5">
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Visible</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinnerBackground">
                                <DiscreteObjectKeyFrame KeyTime="0:0:1">
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Collapsed</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>

                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="spinner">
                                <DiscreteObjectKeyFrame KeyTime="0:0:1">
                                    <DiscreteObjectKeyFrame.Value>
                                        <Visibility>Collapsed</Visibility>
                                    </DiscreteObjectKeyFrame.Value>
                                </DiscreteObjectKeyFrame>
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualTransition>

                </VisualStateGroup.Transitions>

                <VisualState x:Name="ShowAsList">
                    <Storyboard>

                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Contacts">
                            <DiscreteObjectKeyFrame KeyTime="0:0:0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>

                    </Storyboard>
                </VisualState>

                <VisualState x:Name="ShowAsIcons">
                    <Storyboard>

                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Visibility)" Storyboard.TargetName="Avatars">
                            <DiscreteObjectKeyFrame KeyTime="0:0:0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>

                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Border x:Name="ContainerPane" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="White"
                BorderBrush="{StaticResource PrimaryBrush}"
                BorderThickness="2" Margin="2,2,2,0">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <StackPanel Grid.Row="0" HorizontalAlignment="Center" Orientation="Horizontal">
                    <RadioButton x:Name="ShowAsListButton" IsChecked="True"
                                         Content="List"
                                 Margin="5">
                    </RadioButton>
                    <RadioButton
                                x:Name="ShowAsIconsButton"
                                Content="Icons"
                        Margin="5"/>
                </StackPanel>

                <!-- Contacts view-->

                <ListBox x:Name="Contacts"
                             ItemsSource="{Binding Books}"
                             Style="{Binding Source={StaticResource ContactsList}}"
                             HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                             Grid.Row="1" Visibility="Collapsed"/>

                <!-- Avatars view-->

                <ListBox x:Name="Avatars"
                             ItemsSource="{Binding Books}"
                             Style="{Binding Source={StaticResource AvatarsList}}"
                             HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                             Grid.Row="1" Visibility="Collapsed"
                             />
            </Grid>
        </Border>

        <Rectangle x:Name="spinnerBackground" Visibility="Collapsed" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,0" Fill="White" />
        <Viewport3D x:Name="spinner" Visibility="Collapsed" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2,2,2,0">
            <Viewport3D.Camera>
                <PerspectiveCamera x:Name="camera" Position="0,0,0.5" LookDirection="0,0,-1" FieldOfView="90" />
            </Viewport3D.Camera>
            <Viewport3D.Children>
                <ModelVisual3D>
                    <ModelVisual3D.Content>
                        <Model3DGroup>
                            <DirectionalLight Color="#444" Direction="0,0,-1" />
                            <AmbientLight Color="#BBB" />
                        </Model3DGroup>
                    </ModelVisual3D.Content>
                </ModelVisual3D>
                <ModelVisual3D>
                    <ModelVisual3D.Content>
                        <GeometryModel3D>
                            <GeometryModel3D.Geometry>
                                <MeshGeometry3D  TriangleIndices="0,1,2 2,3,0" TextureCoordinates="0,1 1,1 1,0 0,0" Positions="-0.5,-0.5,0 0.5,-0.5,0 0.5,0.5,0 -0.5,0.5,0" />
                            </GeometryModel3D.Geometry>
                            <GeometryModel3D.Material>
                                <DiffuseMaterial>
                                    <DiffuseMaterial.Brush>
                                        <VisualBrush Visual="{Binding ElementName=ContainerPane}" Stretch="Uniform" />
                                    </DiffuseMaterial.Brush>
                                </DiffuseMaterial>
                            </GeometryModel3D.Material>
                            <GeometryModel3D.Transform>
                                <RotateTransform3D>
                                    <RotateTransform3D.Rotation>
                                        <AxisAngleRotation3D x:Name="rotate" Axis="0,3,0" Angle="0" />
                                    </RotateTransform3D.Rotation>
                                </RotateTransform3D>
                            </GeometryModel3D.Transform>
                        </GeometryModel3D>
                    </ModelVisual3D.Content>
                </ModelVisual3D>
            </Viewport3D.Children>
        </Viewport3D>
    </Grid>
   
</Window>
В дизайнере это выглядит следующим образом:
У нас есть две радиокнопки "List" и "Icons", которые при нажатии на них через DataStateBehavior и разные тонкости дизайна просто меняют один контрол на второй, используя для этого красивую анимацию. Поэтому в данном окне и вышло так много кода, который, в основном, делает только анимацию.
Небольшая поправка для разработчиков: для того чтобы у вас заработала строка
<i:Interaction.Behaviors>
            <ei:DataStateBehavior Binding="{Binding IsChecked, ElementName=ShowAsListButton}"
                                  Value="True"
                                  TrueState="ShowAsList" FalseState="ShowAsIcons"/>    
        </i:Interaction.Behaviors>
вам необходимо добавить ссылку на сборку System.Windows.Interactivity.
После проделанных действий перейдем в наш загрузчик Bootstrapper и немного изменим инициализацию нашей оболочки Shell, а точнее, просто установим для нее свойство DataContext.
public class Bootstrapper : UnityBootstrapper
{
    protected override System.Windows.DependencyObject CreateShell()
    {
        return Container.Resolve<Shell>();
    }

    protected override void InitializeShell()
    {
        App.Current.MainWindow = (Window)Shell;
        App.Current.MainWindow.DataContext = Container.Resolve<ShellViewModel>();
        App.Current.MainWindow.Show();
    }
}
И последним этапом у нас будет переопределения метода OnStartup в классе App.xaml.cs, задачей которого будет просто создать наш загрузчик и запустить его.
protected override void OnStartup(StartupEventArgs e)
{
    var bootstrapper = new Bootstrapper();
    bootstrapper.Run();
}
После проделанной работы мы можем запустить наше приложение и посмотреть на результат.
И при нажатии на кнопку "Icons" мы получим следующий результат:
При нажатии на кнопку мы можем увидеть интересный эффект при переключении между контролами. Полный исходный код приложения вы можете скачать по ссылке StateBasedNavigationSample. Если у вас есть неплохой опыт в использовании Expression Blend, то для вас навигация на основе состояний будет очень легкой. Если у вас после прочтения статьи возникли какие-то нюансы и недопонимания в использовании State-Based Navigation, постараюсь на них ответить.