Sni.DelWoods Ответов: 4

На C#: проходя которые можно вывести класс тип в метод в качестве параметра


Идея
В следующем коде используется перегруженный метод, который ожидает базовый класс или конкретный деривированный класс в качестве параметра.

Проблема
Проблема в том, что
FieldTypeBase field = new TextFieldType();
передается как тип FieldTypeBase и звонит Метода Dowork(Упорядочивания По Результатам Вычисления.FieldTypeBase ...)
пока
var field = new TextFieldType();
передается как Свойства textfieldtype и звонит Метода Dowork(Упорядочивания По Результатам Вычисления.Свойства textfieldtype...).

Вопрос:
Есть какие-нибудь идеи, чтобы убедиться, что вызывается "правильный" метод?

Замечание:
Я знаю, что этот код немного пахнет, и, вероятно, будет делать рефакторинг, чтобы реализовать рабочий непосредственно на полях. Но на самом деле я пытаюсь решить именно этот пример.

Что я уже пробовал:

namespace InternalFieldTest
{
    using System.Collections.Generic;
    using FieldTypes;

    public static class Tests
    {
        public static IEnumerable<string> TestWorker()
        {
            FieldTypeBase field1 = new TextFieldType();
            TextFieldType field2 = new TextFieldType();
            
            // failed: DoWork[error:NoWorkerDefined]
            yield return FieldProcessors.DoWork(field1);

            //ok: DoWork[TextFieldType]
            yield return FieldProcessors.DoWork(field2); 

            // ok: DoWork[error:NoWorkerDefined]
            yield return FieldProcessors.DoWork(new UnknownFieldType());
       }
    }
    public static class FieldProcessors
    {
        public static string DoWork(TextFieldType fieldType) => "DoWork[TextFieldType]"; 
        public static string DoWork(FieldTypeBase fieldType) => "DoWork[error:NoWorkerDefined]";
    }

    namespace FieldTypes
    {
        public abstract class FieldTypeBase { }
        public class TextFieldType : FieldTypeBase { }
        public class UnknownFieldType : FieldTypeBase { }
    }

}

4 Ответов

Рейтинг:
31

Tomas Takac

Я предлагаю более объектно-ориентированное решение вашей проблемы. Читайте о шаблоне проектирования двойной отправки. В вашем случае это выглядит так:

1) я бы удалил статические методы. Вы могли бы адаптировать это для использования статических процессоров но вот как я бы это сделал:

public interface IFieldProcessors
{
    string DoWork(TextFieldType fieldType); 
    string DoWork(FieldTypeBase fieldType);
}

public class FieldProcessors : IFieldProcessors
{
    public string DoWork(TextFieldType fieldType) => "DoWork[TextFieldType]"; 
    public string DoWork(FieldTypeBase fieldType) => "DoWork[error:NoWorkerDefined]";
}


2) Добавьте метод отправки к вашим типам полей. Обратите внимание, что именно здесь происходит волшебство, так как для каждого типа поля компилятор точно знает, какой метод процессора вызывать, потому что он знает тип поля. this Для неизвестного поля по умолчанию используется базовый класс, так как более специфической перегрузки нет.
public abstract class FieldTypeBase 
{ 
	public abstract string Dispatch(IFieldProcessors processors);
}

public class TextFieldType : FieldTypeBase
{
	public override string Dispatch(IFieldProcessors processors)
	{
		return processors.DoWork(this);
	}
}

public class UnknownFieldType : FieldTypeBase
{
	public override string Dispatch(IFieldProcessors processors)
	{
		return processors.DoWork(this);
	}
}


3) Проверьте, что вызывается правильный метод:
var field1 = new TextFieldType();
var field2 = new UnknownFieldType();	
var processors = new FieldProcessors();
Console.WriteLine(field1.Dispatch(processors));
Console.WriteLine(field2.Dispatch(processors));

Выход такой:
DoWork[TextFieldType]
DoWork[error:NoWorkerDefined]


Maciej Los

5ed!

Рейтинг:
2

phil.o

Посмотрите на свой код:

public static string DoWork(FieldTypes.FieldTypeBase fieldType) => "DoWork[error:NoWorkerDefined]";
То, что вы интерпретируете как ошибку, - это просто строка, которую вы на самом деле возвращаете в случае, если DoWork(FieldTypes.FieldTypeBase fieldType) метод называется. Может быть, попробуем
public static string DoWork(FieldTypes.FieldTypeBase fieldType) => "DoWork[FieldTypeBase]";

В качестве побочного примечания не используйте var если вызов может быть неоднозначным, вместо этого используйте правильное строго типизированное объявление.


Рейтинг:
1

Sni.DelWoods

Спасибо за ваши комментарии.
Я уже сделал это правильно и переместил функцию в базовый класс.
Приведенный выше код-это всего лишь тестовый фрагмент из более крупной системы am.

Мне просто было интересно, почему система всегда использует версию базового класса.

С моей точки зрения, этот тип четко определяется

FieldTypeBase field1 = new TextFieldType();

Он говорит, что field1 может быть все, что происходит от FieldTypeBase и здесь инициализируется бетоном Свойства textfieldtype... В отражении это так . Тип: TextFieldType, База: FieldTypeBase.

Поэтому я ожидал, что компилятор предпочтет назначенный тип, а не тип объявления.


Richard Deeming

Нет, компилятор всегда работает от объявленного типа переменной. Если вы объявите переменную в качестве базового типа, то компилятор будет использовать именно этот тип.

Представьте себе немного более сложный код:

FieldTypeBase field1 = new TextFieldType();
// Snip 100 lines...
Foo(ref field1);
// Snip 100 lines...
FieldProcessors.DoWork(field1);

Для того чтобы узнать тип времени выполнения значения в field1, компилятор должен был бы вычислить все возможное путь во время выполнения через код между назначением и вызовом метода. Это просто неосуществимо.

И если вы ограничили это "волшебное" разрешение перегрузки до следующей строки, то вы столкнетесь с очень запутанной ситуацией:
FieldTypeBase field1 = new TextFieldType();
FieldProcessors.DoWork(field1); // "DoWork[TextFieldType]"
FieldProcessors.DoWork(field1); // "DoWork[error:NoWorkerDefined]", because it's not the next line

Если вы хотите использовать TextFieldType в разрешении перегрузки вам либо нужно объявить переменную как a TextFieldType, или использовать var и пусть компилятор выведет тип из распределения.

Это звучит так, как будто вы на самом деле ищете Двойной диспетчеризации[^], который часто считается "кодовым запахом". Возможно, вы сможете достичь этого, используя dynamic или паттерн посетителя.

Рейтинг:
0

OriginalGriff

Правильный метод является быть призванным.

FieldTypeBase field = new TextFieldType();

Если вы не увеличиваете переменную базового класса при вызове метода, система должна предположить, что field может содержать любой производный класс и всегда будет вызывать "безопасную" версию - версию базового класса.

Вместо того чтобы делать это таким образом, реализуйте DoWork как метод в базовом классе (и сделайте базовый класс abstract) и позволяют производному классу реализовывать свои собственные версии. Затем система вызовет соответствующую версию для данного типа экземпляра. field содержит.