Kolekcja List<Interface<T>> gdy T jest różne

0

cześć
problem jest taki

mam stepy i chcę dodac je do kolekcji
Stepy sa generyczne

interface IStep<T>
{
    void Enter(T data);
    void Check(IChecker<T> checker);
    void Exit();
}

//potem w kodzie chciałbym mieć takie coś

void StepRunner(IEnumerable<dynamic> steps /*to działa ale ja chciałbym aby było  coś na kształt (IEnumerable<IStep<dynamic>>)*/,T data, (IChecker<T> checker)
{
for(var step in steps)
{
  step.Enter(data);
  step.Check(checker);
    step.Exit();
}
}

udało mi się to zrobić za pomocą dynamic - ale nie podoba mi się to za bardzo bo tracę kontekst np tu Enter i f12 nie przejdzę do interfejsu, nie podgladne, nie zmieni mi się nazwa z automatu, f11 nie wchodzi w metodę itd

czy można jakoś zrobić kolekcję typu

List<IStep<?>> >

3

Ale po co w ogóle ten dynamic?

Nie wiem czy dobrze zrozumiałem problem, ale myślę, że wystarczy poprawić nieco sygnaturę metody:

static void StepRunner<T>(IEnumerable<IStep<T>> steps, T data, IChecker<T> checker)
0

@somekind: bardzo dziękuję za odpowiedź
ale nie o to mi chodziło

Kolekcja List<Interface<T>> gdy T jest różne

czyli mamy w uproszczeniu


var stepA = IStep<A>;
var stepB = IStep<B>;
var stepC = IStep<C>;

List<IStep</*co?*/>> stepy;

na dynamicu działa elegancko, ale ma wady dynamika :)

1

@mussel: To ja odwrócę pytanie. Powiedzmy, że taki kod kompiluje się i działa:

var stepA = new IStep<A>(); // pomijając, że to interfejsy
var stepB = new IStep<B>();
var stepC = new IStep<C>();

var stepy = new List<IStep<Cos>>()
{
    stepA, stepB, stepC
};

I potem wyciągnięcie elementu z takiej listy:

var step1 = stepy[1];
step1.Enter(/*skąd masz wiedzieć, czy podać tu obiekt typu A, B, czy C?*/);

Takie coś jest niemożliwe. Podejrzewam, że to problem XY i błąd jest gdzieś indziej.

0

Hmm, a gdyby każdy z tych obiektów dziedziczył po tym samym interface? Czy to niemożliwe? Wtedy chyba można by zrobić coś typu:

interface IStep<ICos>
        {
            void Enter(ICos data);
            void Exit();
        }

        interface ICos
        {
            void Enter();
        }

        class ObjA : ICos
        {
            public void Enter()
            {
                throw new NotImplementedException();
            }
        }

        class ObjB : ICos
        {
            public void Enter()
            {
                throw new NotImplementedException();
            }
        }

static void StepRunner(IEnumerable<IStep<ICos>> steps, ICos data)
        {
            foreach (var step in steps)
            {
                step.Enter(data);
            }
        }
0

@maszrum: szerszy kontekst

  1. są akcje, które są różnych typów
  • ogólne
  • per klient
  • per typ (typ biznesowy) można powiedzieć że taka receptura
    Taka akcja to step a typ akcji to generyk

2)są procesy, które składają się z tych akcji (stepów)
w zależności od procesu to jakiś krok

  • może być ogólny
  • per klient
  • per typ

np
proces PROCES1 dla klienta A przetwarza A do B - wg typu biznesowego
proces PROCES2 dla klienta A przetwarza A do B - wg już wymagań klienrta

róznica jest taka, że np typ biznesowy ma jakąś normę wytrzymalości i wg niej się liczy, a np klient ma jakieś swoje specyficzne żadania, gdzie coś ma być wytrzymalsze (jakiś próg jest okreslony róznie)

i taki proces buduję sobie ze stepów, a potem go po prostu uruchamiam i proces może mieć taki ciąg:

IStep<GENERAL> -> IStep<CLIENT> -> IStep<BUSINESS_TYPE>-> IStep<CLIENT> -> IStep<CLIENT> -> IStep<GENERAL>

i stąd ta kolekcja stepów

1

Wtrącę się ze Scalą na chwilę. Scala ma obiektowo zorientowane type members, więc można mieć heterogeniczną (w sensie ze względu na generyki) listę. Gdyby C# też miał type members to też mógłby mieć taką listę. Przykład w Scali:

https://scastie.scala-lang.org/CTauirbZQ86V3h0DZZSmZA

trait Checker[T] // generic type parameter

trait Step {
  type T // generic type member

  def make(): T = ???
  def enter(data: T): Unit = ???
  def check(checker: Checker[T]): Unit = ???
  def exit(): Unit = ???
}

class StepString extends Step {
  override type T = String
}

class StepInt extends Step {
  override type T = Int
}

val steps: List[Step] = List(new StepString, new StepInt)
val step0 = steps(0)
val step1 = steps(1)

step0.enter(step0.make()) // compiles
step1.enter(step1.make()) // compiles

step1.enter(step0.make()) // doesn't compile
step0.enter(step1.make()) // doesn't compile
0
mussel napisał(a):

róznica jest taka, że np typ biznesowy ma jakąś normę wytrzymalości i wg niej się liczy, a np klient ma jakieś swoje specyficzne żadania, gdzie coś ma być wytrzymalsze (jakiś próg jest okreslony róznie)

i taki proces buduję sobie ze stepów, a potem go po prostu uruchamiam i proces może mieć taki ciąg:

IStep<GENERAL> -> IStep<CLIENT> -> IStep<BUSINESS_TYPE>-> IStep<CLIENT> -> IStep<CLIENT> -> IStep<GENERAL>

W C# typy generyczne służą do generalizacji, czyli uogólniania, a o ile rozumiem Twój opis, to Ty po prostu nie masz jak uogólnić, więc to nie działa.
Myślałeś, żeby wprowadzić jakiś bazowy typ dla akcji, albo mieć niegeneryczny IStep?

0

@somekind: ma rację. W takich sytuacjach robi się dodatkowy interfejs, czyli:

public interface IStep
{
 //coś wspólnego dla wszystkich niezależnie od typu
}

public interface IStep<T>: IStep
{

}


//a później po prostu:
List<IStep> list;
1

A nie możesz mieć w ten sposób, że interface wywołuje metodę bez parametrów? Ale dane na których ma operować dostarczasz podczas tworzenia konkretnego stepu. I wtedy nie masz problemu z przekazaniem do procesu różnych typów IStep.

class Program
    {
        static void Main(string[] args)
        {
            IStep test = new BusinessStep(1);
            test.Enter();
        }
        
        
    }
    
    public interface IStep
    {
        public void Enter();
        public void Check();
    }

    public class BusinessStep : BasicStep<int>
    {
        public BusinessStep(int data) :base(data)
        {
        }
        public override void Enter()
        {
            //your implementation
        }

        public override void Check()
        {
            throw new NotImplementedException();
        }
    }

    public abstract class BasicStep<T> : IStep
    {
        protected T data;
        protected BasicStep(T data)
        {
            this.data = data;
        }
        public abstract void Enter();

        public abstract void Check();
    }
0

Wychodzi na to samo co opakować klasę w interfejs. Nie rozumiem problemu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1