Использование Func через интерфейс?

у меня уже есть существующий универсальный класс

public class Foo<T>
{
    private T _item;
    public Foo(T item){ _item = item;}
}

Я должен создать метод, который вернет определенное свойство T.
Я вижу здесь два решения.

  1. создание интерфейса :

    public const string TestVar = "bar";
    public interface INamable { string Name { get; } }
    public class Bar : INamable
    {
        public string Name { get { return TestVar; } }
    }
    
    public class Foo<T> where T : INamable
    {
        private T _item;
        public Foo(T item) { _item = item; }
        public string GetName() { return this._item.Name; }
    }
    
    [TestMethod()]
    public void TestInterface()
    {
        var bar = new Bar();
        var foo = new Foo<Bar>(bar);
        Assert.AreEqual(TestVar, foo.GetName());
    }
    
  2. передача функции:

    public const string TestVar = "bar";
    public class Bar
    {
        public string Name { get { return TestVar; } }
    }
    
    public class Foo<T>
    {
        private T _item;
        private Func<T, string> _funcName;
        public Foo(T item, Func<T, string> funcName) { _item = item; _funcName = funcName; }
        public string GetName() { return _funcName.Invoke(_item); }
    }
    
    [TestMethod()]
    public void TestFunc()
    {
        var bar = new Bar();
        var foo = new Foo<Bar>(bar, b => b.Name);
        Assert.AreEqual(TestVar, foo.GetName());
    }
    

Я иду со вторым решением, так как мне не нужно создавать интерфейс, и я ленив. Просто нужно добавить один параметр в уже вызов ФОО конструктор.

кроме того, класс Foo все еще может использоваться со всеми видами класса, и я предпочитаю его таким образом.

но я не уверен, что это хороший способ использовать Func? Она все еще хорошая и прочная? Это моя первая попытка использовать Func, вот почему я спрашиваю себя!

пару месяцев назад я использовал первое решение, с классическим интерфейсом...

4 ответов


я перефразирую свой ответ, потому что из комментариев я понял, что я не очень ясно.

Это зависит от намерения вашего кода и от того, кто несет ответственность за то, чтобы дать имя вашим классам (клиент должен знать и указывать, как называть классы против классов, которые по своей сути должны иметь имя). я немного меняю пример @Scorpi0, так что Foo действительно делает что-то с именем (он приветствует его), а не просто возвращает его.

Пример 1 (интерфейсы)

interface INamable { string Name { get; } }

class AThingWithAName : INamable 
{
    public string Name {get {return "thing";}}
}

class AnotherThingWithAName : INamable 
{
    public string Name {get {return "different thing";}}
}

class Foo<T> where T : INamable
{
    public string Greet(T item) {return "hi " + item;}
}

здесь вещи имеют имя независимо от Foo. Foo знает, как приветствовать вещи, пока у них есть имя (которое гарантируется контрактом, реализуя интерфейс INamable). Здесь вы хотите гарантировать, что Foo работает только на вещах с именем, а попытки приветствовать что-либо еще должны быть ошибкой типа, пойманной компилятором.

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

Пример 2 (Func)

class AThing {}
class AnotherUnrelatedThing {}

class Foo<T>
{
    public string Greet(T item, Func<T, string> namingFunction) 
    {
        return "hi " + namingFunction(item);
    }
}

здесь акцент делается на Foo. Вещи не обязательно имеют название. Я хочу создать класс Foo, который может приветствовать что угодно. Как он это делает? Ну, это ответственность вызывающего абонента, для любого типа, передать функцию, которая может дать имя этому типу. В этом случае мы не только не запрашиваем гарантию, что T знает свое собственное имя, но мы строим Foo таким образом, что он может приветствовать любой тип, пока мы знаю, как дать ему имя. Например это работает:

var foo = new Foo<int>();
var res=foo.Greet(2, n=>n.ToString());

в обмен на эту гибкость, мы сдаемся заключения. Теперь Name больше не является тем, что несет ответственность за реализацию класса, мы (вызывающий) говорим Foo, как дать имя каждому классу, с которым мы его используем.

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


первое решение (т. е. без Func) намного проще. Нет абсолютно никакой необходимости вызывать объекты Func для реализации метода доступа к свойствам garden-variety.


если ваш интерфейс будет в конечном итоге только с одним методом, это совершенно допустимо использовать Action или Func делегаты вместо этого. Это намного чище, имхо.

вы не должны писать Invoke хотя. Просто назовите его в скобках:_funcName(_item)


делегаты во многих случаях будут более удобными для разработчиков, потому что оба vb.net и компиляторы C# включают функции, чтобы сделать их очень простыми в использовании. Интерфейсы могут делать некоторые вещи, которые делегаты не могут (например, включать открытые общие семейства методов), и в некоторых случаях могут быть более эффективными, чем делегаты (поскольку ссылка на объект, который реализует интерфейс, может быть передана как этот тип интерфейса, без необходимости создавать другой объект кучи для его хранения). Факт однако то, что компиляторы поддерживают простое создание встроенных делегатов, не предоставляя сопоставимых возможностей для однофункциональных интерфейсов, представляет собой очень сильный аргумент в пользу первого.