Condivisione della tecnologia

WPF/C#: come implementare l'inserimento delle dipendenze in WPF

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Prefazione

Questo articolo usa il progetto WPF Gallery per apprendere i concetti correlati all'inserimento delle dipendenze e come eseguire l'inserimento delle dipendenze in WPF.

Cos'è l'iniezione di dipendenza

Dependency Injection (DI) è un modello di progettazione utilizzato per implementare il principio di inversione del controllo (IoC). Lo scopo principale dell'iniezione delle dipendenze è trasferire la creazione di oggetti e la gestione delle dipendenze tra oggetti dall'interno dell'oggetto a un contenitore o framework esterno, migliorando così la manutenibilità, la testabilità e la flessibilità del codice.

Il concetto centrale dell'iniezione di dipendenza

  1. fare affidamento : Un oggetto necessita di un altro oggetto per completare il suo lavoro, quindi il primo dipende dal secondo. Ad esempio, una classe OrderService potrebbe fare affidamento su una classe ProductRepository per ottenere informazioni sul prodotto.
  2. iniezione : passa l'oggetto dipendente all'oggetto che ne ha bisogno, invece di lasciare che sia l'oggetto che ne ha bisogno a creare l'oggetto dipendente stesso. L'iniezione può essere ottenuta tramite costruttori, proprietà o parametri del metodo.
  3. contenitore : un framework o libreria che gestisce la creazione di oggetti e le dipendenze. Il contenitore è responsabile della creazione di istanze di oggetti, della risoluzione delle dipendenze e dell'inserimento di oggetti dipendenti negli oggetti che li richiedono.

Tipi di inserimento delle dipendenze

iniezione del costruttore: L'oggetto dipendente viene passato attraverso il costruttore della classe.

public class OrderService
{
    private readonly IProductRepository _productRepository;

    public OrderService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Iniezione di proprietà: gli oggetti dipendenti vengono passati attraverso le proprietà pubbliche della classe.

public class OrderService
{
    public IProductRepository ProductRepository { get; set; }
}
  • 1
  • 2
  • 3
  • 4

metodo di iniezione: L'oggetto dipendente viene passato attraverso il parametro del metodo della classe.

public class OrderService
{
    public void ProcessOrder(IProductRepository productRepository)
    {
        // 使用 productRepository 处理订单
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Perché è necessaria l'iniezione di dipendenza

Dependency Injection (DI) è un modello di progettazione attraverso il quale la creazione di oggetti e la gestione delle dipendenze tra oggetti possono essere trasferite dall'interno dell'oggetto a un contenitore o framework esterno. Esistono diversi motivi e vantaggi importanti per l'inserimento delle dipendenze:

  1. Ridurre l'accoppiamento : L'iniezione delle dipendenze trasferisce la gestione delle dipendenze dall'interno dell'oggetto al contenitore esterno, in modo che l'oggetto non abbia bisogno di sapere come creare gli oggetti da cui dipende, ma deve solo conoscere l'interfaccia dell'oggetto dipendente. Ciò può ridurre significativamente l'accoppiamento tra gli oggetti e rendere il codice più modulare e flessibile.
  2. Migliora la testabilità : L'inserimento delle dipendenze rende i test unitari più semplici ed efficienti. Utilizzando oggetti fittizi o stub invece di oggetti dipendenti effettivi, gli sviluppatori possono eseguire test unitari senza fare affidamento sull'implementazione effettiva. Ciò aiuta a garantire l’indipendenza e l’affidabilità del test.
  3. Migliorare la manutenibilità : Poiché l'iniezione delle dipendenze riduce l'accoppiamento tra gli oggetti, il codice diventa più modulare e chiaro. Ciò rende il codice più facile da comprendere e mantenere. Quando è necessario modificare o sostituire un oggetto dipendente, è necessario modificare solo le informazioni di configurazione o di registrazione, senza modificare il codice che utilizza l'oggetto.
  4. Migliorare la flessibilità : L'inserimento delle dipendenze rende il sistema più flessibile e può facilmente sostituire gli oggetti dipendenti per ottenere funzioni o comportamenti diversi. Ad esempio, è possibile commutare diverse implementazioni del livello di accesso al database tramite file di configurazione o codice senza modificare il codice della logica aziendale.
  5. Promuovere la separazione delle preoccupazioni : L'iniezione di dipendenza aiuta a raggiungere la separazione delle preoccupazioni (Separation of Concerns), in modo che ogni oggetto debba concentrarsi solo sulle proprie responsabilità, senza preoccuparsi di come creare e ottenere gli oggetti da cui dipende. Ciò aiuta a migliorare la chiarezza e la manutenibilità del codice.
  6. Supportare modelli di progettazione e best practice : L'inserimento delle dipendenze è la base di molti modelli di progettazione e best practice, come Inversion of Control (IoC), Service Locator Pattern, ecc. Utilizzando l'inserimento delle dipendenze, gli sviluppatori possono implementare più facilmente questi modelli e pratiche, migliorando così la qualità e la scalabilità del codice.

Come implementare l'inserimento delle dipendenze

Questo articolo utilizza il progetto Galleria WPF per apprendere come utilizzare l'inserimento delle dipendenze nell'indirizzo del codice WPF:

https://github.com/microsoft/WPF-Samples/blob/main/SampleApplications/WPFGallery

Per implementare l'inserimento delle dipendenze in questo progetto, vengono utilizzati questi due pacchetti:

immagine-20240711100435001

Per prima cosa guarda il contenuto di App.xaml.cs:

public partial class App : Application
{

    private static readonly IHost _host = Host.CreateDefaultBuilder()
        .ConfigureServices((context, services) =>
        {
            services.AddSingleton<INavigationService, NavigationService>();
            services.AddSingleton<MainWindow>();
            services.AddSingleton<MainWindowViewModel>();
            
            services.AddTransient<DashboardPage>();
            services.AddTransient<DashboardPageViewModel>();

            services.AddTransient<ButtonPage>();
            services.AddTransient<ButtonPageViewModel>();
            services.AddTransient<CheckBoxPage>();
            services.AddTransient<CheckBoxPageViewModel>();
            services.AddTransient<ComboBoxPage>();
            services.AddTransient<ComboBoxPageViewModel>();
            services.AddTransient<RadioButtonPage>();
            services.AddTransient<RadioButtonPageViewModel>();
            services.AddTransient<SliderPage>();
            services.AddTransient<SliderPageViewModel>();
            services.AddTransient<CalendarPage>();
            services.AddTransient<CalendarPageViewModel>();
            services.AddTransient<DatePickerPage>();
            services.AddTransient<DatePickerPageViewModel>();
            services.AddTransient<TabControlPage>();
            services.AddTransient<TabControlPageViewModel>();
            services.AddTransient<ProgressBarPage>();
            services.AddTransient<ProgressBarPageViewModel>();
            services.AddTransient<MenuPage>();
            services.AddTransient<MenuPageViewModel>();
            services.AddTransient<ToolTipPage>();
            services.AddTransient<ToolTipPageViewModel>();
            services.AddTransient<CanvasPage>();
            services.AddTransient<CanvasPageViewModel>();
            services.AddTransient<ExpanderPage>();
            services.AddTransient<ExpanderPageViewModel>();
            services.AddTransient<ImagePage>();
            services.AddTransient<ImagePageViewModel>();
            services.AddTransient<DataGridPage>();
            services.AddTransient<DataGridPageViewModel>();
            services.AddTransient<ListBoxPage>();
            services.AddTransient<ListBoxPageViewModel>();
            services.AddTransient<ListViewPage>();
            services.AddTransient<ListViewPageViewModel>();
            services.AddTransient<TreeViewPage>();
            services.AddTransient<TreeViewPageViewModel>();
            services.AddTransient<LabelPage>();
            services.AddTransient<LabelPageViewModel>();
            services.AddTransient<TextBoxPage>();
            services.AddTransient<TextBoxPageViewModel>();
            services.AddTransient<TextBlockPage>();
            services.AddTransient<TextBlockPageViewModel>();
            services.AddTransient<RichTextEditPage>();
            services.AddTransient<RichTextEditPageViewModel>();
            services.AddTransient<PasswordBoxPage>();
            services.AddTransient<PasswordBoxPageViewModel>();
            services.AddTransient<ColorsPage>();
            services.AddTransient<ColorsPageViewModel>();

            services.AddTransient<LayoutPage>();
            services.AddTransient<LayoutPageViewModel>();
            services.AddTransient<AllSamplesPage>();
            services.AddTransient<AllSamplesPageViewModel>();
            services.AddTransient<BasicInputPage>();
            services.AddTransient<BasicInputPageViewModel>();
            services.AddTransient<CollectionsPage>();
            services.AddTransient<CollectionsPageViewModel>();
            services.AddTransient<MediaPage>();
            services.AddTransient<MediaPageViewModel>();
            services.AddTransient<NavigationPage>();
            services.AddTransient<NavigationPageViewModel>();
            services.AddTransient<TextPage>();
            services.AddTransient<TextPageViewModel>();
            services.AddTransient<DateAndTimePage>();
            services.AddTransient<DateAndTimePageViewModel>();
            services.AddTransient<StatusAndInfoPage>();
            services.AddTransient<StatusAndInfoPageViewModel>();
            services.AddTransient<SamplesPage>();
            services.AddTransient<SamplesPageViewModel>();
            services.AddTransient<DesignGuidancePage>();
            services.AddTransient<DesignGuidancePageViewModel>();

            services.AddTransient<UserDashboardPage>();
            services.AddTransient<UserDashboardPageViewModel>();

            services.AddTransient<TypographyPage>();
            services.AddTransient<TypographyPageViewModel>();

            services.AddSingleton<IconsPage>();
            services.AddSingleton<IconsPageViewModel>();

            services.AddSingleton<SettingsPage>();
            services.AddSingleton<SettingsPageViewModel>();

            services.AddSingleton<AboutPage>();
            services.AddSingleton<AboutPageViewModel>();
        }).Build();


    [STAThread]
    public static void Main()
    {
        _host.Start();

        App app = new();
        app.InitializeComponent();
        app.MainWindow = _host.Services.GetRequiredService<MainWindow>();
        app.MainWindow.Visibility = Visibility.Visible;
        app.Run();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

immagine-20240711083011393

Cos'è IHost?

In C#,IHost è un'interfaccia utilizzata in .NET per creare e configurare applicazioniHostconcetto astratto.IHost Un'interfaccia definisce una raccolta di servizi e componenti necessari per avviare, eseguire e gestire un'applicazione. Viene in genere utilizzato con le applicazioni ASP.NET Core, ma funziona anche con altri tipi di applicazioni .NET, ad esempio applicazioni console o programmi WPF.

immagine-20240711082817268

IHostL'interfaccia è composta daHostBuilderImplementazione della classe, che prevede la creazione e la configurazione diIHostmetodi di istanza.HostBuilderConsente di aggiungere vari servizi come registrazione, configurazione, contenitori di inserimento delle dipendenze, ecc. e di configurare il comportamento di avvio e arresto dell'applicazione.

immagine-20240711083048854

immagine-20240711083156306

Fornisce metodi pratici per la creazione di istanze Microsoft.Extensions.Hosting.IHostBuilder con impostazioni predefinite preconfigurate.

immagine-20240711083713204

Restituisce un IHostBuilder.

immagine-20240711084145756

immagine-20240711084211035

Aggiungi servizi al contenitore. Questa operazione può essere richiamata più volte e i suoi risultati sono cumulativi.

Il significato del parametro configureDelegate è configurare il delegato di Microsoft.Extensions.DependencyInjection.IServiceCollection.
Questa raccolta verrà utilizzata per costruire System.IServiceProvider.

Il delegato richiede due tipi di parametri: HostBuilderContext e IServiceCollection, che non hanno valore restituito.

immagine-20240711084853971

Qui viene passata un'espressione Lambda che soddisfa il tipo delegato.

In C#,() => {} È una sintassi per le espressioni Lambda. Le espressioni Lambda sono un wrapper delegato leggero che consente di definire un metodo anonimo e passarlo come parametro a un metodo che supporta delegati o alberi delle espressioni.

Le espressioni lambda forniscono un modo conciso per definire i metodi e sono utili soprattutto quando è necessario passare metodi come parametri ad altri metodi.

immagine-20240711085344696

Quando si aggiungono servizi, qui ci sono due cicli di vita, oltre a AddSingleton e AddTransient, c'è anche AddScoped.

Questi metodi definiscono il ciclo di vita del servizio, ovvero il modo in cui le istanze del servizio vengono create e gestite nell'applicazione.

AggiungiSingleton

  • ciclo vitale:Singleton
  • Senso : viene creata una sola istanza del servizio durante l'intero ciclo di vita dell'applicazione. Non importa quante volte lo richiedi dal contenitore, verrà restituita la stessa istanza.
  • Scena applicabile: adatto per servizi senza stato o risorse condivise nell'intera applicazione, come configurazioni, logger, ecc.

AggiungiTransiente

  • ciclo vitale: Transitorio
  • Senso: ogni volta che viene richiesto un servizio dal contenitore, viene creata una nuova istanza.
  • Scena applicabile: adatto per servizi con stato o scenari che richiedono una nuova istanza per ogni richiesta, ad esempio pagine, modelli di visualizzazione e così via.

Aggiungi ambito

  • ciclo vitale: Ambito
  • Senso : all'interno di ciascun ambito, le istanze del servizio sono univoche. Gli ambiti sono spesso associati al ciclo di vita di una richiesta, ad esempio nelle applicazioni web viene creato un nuovo ambito con ogni richiesta HTTP.
  • Scena applicabile: adatto per servizi che devono condividere istanze nell'ambito della richiesta, come i contesti di database.

utilizzare questi servizi

Nella funzione principale:

immagine-20240711095100016

avviare_host,passaggio_host.Services.GetRequiredService<MainWindow>();Ottieni l'istanza MainWindow.

Prendendo come esempio la classe MainWindow, visualizza il costruttore di MainWindow in MainWindow.xaml.cs:

public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{
    _serviceProvider = serviceProvider;
    ViewModel = viewModel;
    DataContext = this;
    InitializeComponent();

    Toggle_TitleButtonVisibility();

    _navigationService = navigationService;
    _navigationService.Navigating += OnNavigating;
    _navigationService.SetFrame(this.RootContentFrame);
    _navigationService.Navigate(typeof(DashboardPage));

    WindowChrome.SetWindowChrome(
        this,
        new WindowChrome
        {
            CaptionHeight = 50,
            CornerRadius = default,
            GlassFrameThickness = new Thickness(-1),
            ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4),
            UseAeroCaptionButtons = true
        }
    );

    this.StateChanged += MainWindow_StateChanged;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Dopo aver rimosso il contenuto non pertinente a questo argomento, risulta quanto segue:

public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider, INavigationService navigationService)
{
    _serviceProvider = serviceProvider;
    ViewModel = viewModel; 
    _navigationService = navigationService;  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Hai notato che non hai più bisogno di creare nuovi oggetti da solo? La creazione di questi oggetti è gestita dal contenitore di iniezione delle dipendenze. Quando questi oggetti sono necessari, possono essere iniettati tramite il costruttore come adesso.

Se l'inserimento delle dipendenze non viene utilizzato, potrebbe assomigliare a questo:

public MainWindow()
{
    _serviceProvider = new IServiceProvider();
    ViewModel = new MainWindowViewModel(); 
    _navigationService = new INavigationService();  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Riassumere

Questo articolo introduce innanzitutto il concetto di inserimento delle dipendenze, quindi spiega perché è necessario l'inserimento delle dipendenze e infine illustra come utilizzare l'inserimento delle dipendenze in WPF tramite il progetto WPF Gallery.

fare riferimento a

1、[WPF-Samples/Sample Applications/WPFGallery su main · microsoft/WPF-Samples (github.com)](https://github.com/microsoft/WPF-Samples/tree/main/Sample Applications/WPFGallery)