Обновление двух общих функций для использования Generics

Используя Microsoft Unit Test Wizard, он создает объекты Accessor, если вам нужно проверить непубличное свойство в другом проекте. В своих модульных тестах я создаю вспомогательные функции, чтобы не повторять один и тот же код только в каждом методе модульного теста. В настоящее время у меня есть два теста, которые практически идентичны, за исключением того, что один принимает стандартный объект, а другой - версию Accessor. Так как Accessor основан на стандартной версии, у меня должна быть одна функция, и я полагаю, что должен иметь возможность использовать Generics для выполнения. Проблема пытается перепечатывать и компилировать сбои.

Вот две существующие функции:

// Common function to create a new test record with standard Account object
internal static void CreateAccount(out Account account, bool saveToDatabase)
{
    DateTime created = DateTime.Now;
    string createdBy = _testUserName;

    account = new Account(created, createdBy);

    account.Notes = Utilities.RandomString(1000);

    if (saveToDatabase)
        account.Create();
}

// Common function to create a new test record with Account_Accessor
internal static void CreateAccount(out Account_Accessor account, bool saveToDatabase)
{
    DateTime created = DateTime.Now;
    string createdBy = _testUserName;

    account = new Account_Accessor(created, createdBy);

    account.Notes = Utilities.RandomString(1000);

    if (saveToDatabase)
        account.Create();
}

Я попытался изменить подпись комбинированной функции на:

internal static void CreateAccount<T>(out T account, bool saveToDatabase) {...}

но не удалось правильно переписать T в Account или Account_Accessor. Какие-либо предложения?

2 ответа

Решение

Вы должны добавить ограничение к универсальной функции, потому что это два метода:

account.Notes = Utilities.RandomString(1000);
account.Create();

Я предлагаю вам добавить некоторый интерфейс с этими двумя методами и добавить наследование от него к вашим двум классам. Ограничение должно быть следующим:

where T : YourNewInterface

Об ограничениях вы можете прочитать по http://msdn.microsoft.com/en-us/library/bb384067.aspx

ОБНОВИТЬ

public interface IAccount
    {
        string Notes { get; set; }
        void Create();
        void Init(DateTime created, string createdBy);
    }

public class Account : IAccount
{
    public string Notes
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public void IAccount.Create()
    {
        throw new NotImplementedException();
    }

    void IAccount.Init(DateTime created, string createdBy)
    {
        throw new NotImplementedException();
    }
}

public class Account_Accessor : IAccount
{

    string IAccount.Notes
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public void IAccount.Create()
    {
        throw new NotImplementedException();
    }

    void IAccount.Init(DateTime created, string createdBy)
    {
        throw new NotImplementedException();
    }
}


class Program
{
    internal static void CreateAccount<T>(out T account, bool saveToDatabase) where T : IAccount,new()
    {
        DateTime created = DateTime.Now;
        string createdBy = _testUserName;

        account = new T();
        account.Init(created, createdBy);

        account = (T)Activator.CreateInstance(typeof(T), new object[] { created, createdBy });

        account.Notes = Utilities.RandomString(1000);

        if (saveToDatabase)
            account.Create();
    }
    static void Main(string[] args)
    {
        Account acc;
        Account_Accessor acc2;
        CreateAccount(out acc, false);
        CreateAccount(out acc2, false);
    }
}

Вот несколько комментариев о моем примере:
1. Я заменил CreateInstance добавляя new() ограничение.
2. Поскольку ограничение new() не может иметь параметров из-за общих ограничений.NET, я добавил Init() метод к IAccount интерфейс.
3. Init метод не должен вызываться клиентским кодом Account класс, поэтому мы определяем метод как частный и явно для IAccount.
4. Из-за new() ограничение вы должны предоставить конструктор без параметров для Account, Если вы сделаете это, ваш клиентский код не должен вызывать этот ctor без параметров.

Что касается меня, я бы ушел Activator.CreateInstance как есть. Это хороший обходной путь для ограничений общего new() ограничение

Вот мое мнение о том, чтобы сделать этот метод общим.

public abstract class BaseAccount
{
    public string Notes;

    public virtual void Create() { ... }
}

public class Account : BaseAccount { ... }

public class Account_Accessor : BaseAccount { ... }

internal static void CreateAccount<T>(out T account, bool saveToDatabase) where T : BaseAccount
{
    DateTime created = DateTime.Now;
    string createdBy = _testUserName;

    account = (T)Activator.CreateInstance(typeof(T), new object[] { created, createdBy });

    account.Notes = Utilities.RandomString(1000);

    if (saveToDatabase)
        account.Create();
}

Я предполагаю, что Account и Account_Accessor достаточно похожи, чтобы иметь общий класс hiearchy или реализовывать один и тот же интерфейс. В этом примере я предоставил абстрактный класс, из которого они оба получены, но вместо этого очень легко сделать это с помощью интерфейса. Тем не менее, полная реализация должна быть сделана в обоих классах.

Зная это, я могу ограничить универсальный метод так, чтобы T был только потомком BaseAccount. Таким образом, я могу получить доступ к члену из этого базового класса, не зная реального типа T.

Другие вопросы по тегам