Step-by-Step Theme Implementation Guide

1. Set up Theme Structure

First, create a theme structure:

// theme/base.ts
export const baseTheme = {
  spacing: 8,
  typography: {
    fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
  },
  // Common theme properties
};

// theme/light.ts
import { baseTheme } from './base';

export const lightTheme = {
  ...baseTheme,
  palette: {
    mode: 'light' as const,
    primary: {
      main: '#1976d2',
    },
    background: {
      default: '#ffffff',
      paper: '#f5f5f5',
    },
    text: {
      primary: '#000000',
      secondary: '#666666',
    },
  },
};

// theme/dark.ts
import { baseTheme } from './base';

export const darkTheme = {
  ...baseTheme,
  palette: {
    mode: 'dark' as const,
    primary: {
      main: '#90caf9',
    },
    background: {
      default: '#121212',
      paper: '#1e1e1e',
    },
    text: {
      primary: '#ffffff',
      secondary: '#b3b3b3',
    },
  },
};

// theme/index.ts
export { lightTheme } from './light';
export { darkTheme } from './dark';
export { baseTheme } from './base';

2. Create a Theme Store with Zustand

Create a theme store:

// hooks/useTheme.tsx
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { lightTheme, darkTheme } from '../theme';

type ThemeMode = 'light' | 'dark';

interface ThemeState {
  mode: ThemeMode;
  theme: typeof lightTheme | typeof darkTheme;
  toggleTheme: () => void;
  setTheme: (mode: ThemeMode) => void;
}

export const useTheme = create<ThemeState>()(
  persist(
    (set, get) => ({
      mode: 'light',
      theme: lightTheme,
      toggleTheme: () => {
        const currentMode = get().mode;
        const newMode = currentMode === 'light' ? 'dark' : 'light';
        set({
          mode: newMode,
          theme: newMode === 'light' ? lightTheme : darkTheme,
        });
      },
      setTheme: (mode: ThemeMode) => {
        set({
          mode,
          theme: mode === 'light' ? lightTheme : darkTheme,
        });
      },
    }),
    {
      name: `${window.appName || 'app'}-theme-storage`,
      partialize: (state) => ({ mode: state.mode }),
    }
  )
);

3. Create Theme Provider Component

// components/ThemeProvider.tsx
import React, { createContext, useContext, ReactNode } from 'react';
import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
import { createTheme } from '@mui/material/styles';
import { useTheme } from '../hooks/useTheme';

const ThemeContext = createContext(null);

interface ThemeProviderProps {
  children: ReactNode;
}

export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
  const { theme } = useTheme();
  
  const muiTheme = createTheme(theme);

  return (
    <MuiThemeProvider theme={muiTheme}>
      <CssBaseline />
      {children}
    </MuiThemeProvider>
  );
};

4. Create Theme Toggle Component

// components/ThemeToggle.tsx
import React from 'react';
import { IconButton, Tooltip } from '@mui/material';
import { Brightness4, Brightness7 } from '@mui/icons-material';
import { useTheme } from '../hooks/useTheme';

export const ThemeToggle: React.FC = () => {
  const { mode, toggleTheme } = useTheme();

  return (
    <Tooltip title={`Switch to ${mode === 'light' ? 'dark' : 'light'} mode`}>
      <IconButton onClick={toggleTheme} color="inherit">
        {mode === 'light' ? <Brightness4 /> : <Brightness7 />}
      </IconButton>
    </Tooltip>
  );
};

5. Set up Global App Name

In your main.tsx file:

// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

// Set the app name for theme storage
declare global {
  interface Window {
    appName: string;
  }
}

window.appName = 'your-app-name';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

6. Wrap Your App with Theme Provider

// App.tsx
import React from 'react';
import { ThemeProvider } from './components/ThemeProvider';
import { ThemeToggle } from './components/ThemeToggle';
import { AppBar, Toolbar, Typography, Box } from '@mui/material';

function App() {
  return (
    <ThemeProvider>
      <Box sx={{ flexGrow: 1 }}>
        <AppBar position="static">
          <Toolbar>
            <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
              My App
            </Typography>
            <ThemeToggle />
          </Toolbar>
        </AppBar>
        {/* Your app content */}
        <Box sx={{ p: 3 }}>
          <Typography variant="h4">Welcome to My App</Typography>
          <Typography variant="body1">
            This app supports dark and light themes!
          </Typography>
        </Box>
      </Box>
    </ThemeProvider>
  );
}

export default App;

7. Install Required Dependencies

npm install zustand @mui/material @mui/icons-material @emotion/react @emotion/styled

8. Add CSS Variables (Optional)

For additional CSS customization, you can also add CSS variables:

/* index.css */
:root {
  --primary-color: #1976d2;
  --background-color: #ffffff;
  --text-color: #000000;
}

[data-theme='dark'] {
  --primary-color: #90caf9;
  --background-color: #121212;
  --text-color: #ffffff;
}

9. Advanced: Theme Persistence with System Preference

// hooks/useTheme.tsx (enhanced version)
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { lightTheme, darkTheme } from '../theme';

type ThemeMode = 'light' | 'dark' | 'system';

interface ThemeState {
  mode: ThemeMode;
  effectiveMode: 'light' | 'dark';
  theme: typeof lightTheme | typeof darkTheme;
  toggleTheme: () => void;
  setTheme: (mode: ThemeMode) => void;
}

const getSystemTheme = (): 'light' | 'dark' => {
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};

const getEffectiveMode = (mode: ThemeMode): 'light' | 'dark' => {
  return mode === 'system' ? getSystemTheme() : mode;
};

export const useTheme = create<ThemeState>()(
  persist(
    (set, get) => ({
      mode: 'system',
      effectiveMode: getSystemTheme(),
      theme: getSystemTheme() === 'light' ? lightTheme : darkTheme,
      toggleTheme: () => {
        const currentMode = get().mode;
        const newMode = currentMode === 'light' ? 'dark' : 'light';
        const effectiveMode = getEffectiveMode(newMode);
        set({
          mode: newMode,
          effectiveMode,
          theme: effectiveMode === 'light' ? lightTheme : darkTheme,
        });
      },
      setTheme: (mode: ThemeMode) => {
        const effectiveMode = getEffectiveMode(mode);
        set({
          mode,
          effectiveMode,
          theme: effectiveMode === 'light' ? lightTheme : darkTheme,
        });
      },
    }),
    {
      name: `${window.appName || 'app'}-theme-storage`,
      partialize: (state) => ({ mode: state.mode }),
    }
  )
);

Key Benefits of This Approach:

  1. Persistent Theme: Uses Zustand with persistence to remember user preference
  2. Material-UI Integration: Seamlessly works with MUI components
  3. Type Safety: Full TypeScript support
  4. Flexible: Easy to extend with more themes or custom properties
  5. Performance: Minimal re-renders with Zustand
  6. System Integration: Optional system theme preference support

Comparing different ways to loop through a collection in C#

I think it is very convenient the use of the ForEach statement when we want to loop through a list of items in C#. Simple and concise, easy to read but what is going on behind the scenes? Is the best option? This article will explore each option in detail.

Let’s start:

The old fashioned FOR loop

The for statement executes a statement or a block of statements while a specified Boolean expression evaluates to true. We just need to define a starting point and an increment pattern of the variable used as index.  

        public void ForLoop()
        {
            sum = 0;
            for (int i = 0; i < elements.Count; i++)
            {
                sum += elements[i];
            }
        }

The compiler translates/refactors high-level language constructs to lower-level language constructs. This is important to understand as it could compromise the performance of your code. All these transformations happen before your code is compiled into IL, this step is called Lowering.

After lowering, the code would look like this:

    public void ForLoop()
    {
        sum = 0;
        int num = 0;
        while (num < elements.Count)
        {
            sum += elements[num];
            num++;
        }
    }

Ok, that’s interesting! The for statement has been replaced by while.


The cool kid, ForEach.

The foreach statement executes a statement or a block of statements for each element in an instance of the type that implements the System.Collections.IEnumerable or System.Collections.Generic.IEnumerable<T> interface.

        public void ForEachLoop()
        {
            sum = 0;
            foreach (var element in elements)
            {
                sum += element;
            }
        }

Without a doubt, this option is easier to read and understand as there is no need of indexes and increment patterns. After lowering, the code looks as follows:

    public void ForEachLoop()
    {
        sum = 0;
        List<int>.Enumerator enumerator = elements.GetEnumerator();
        try
        {
            while (enumerator.MoveNext())
            {
                int current = enumerator.Current;
                sum += current;
            }
        }
        finally
        {
            ((IDisposable)enumerator).Dispose();
        }
    }

OK.. there’s some magic here, the code now includes try/finally in order to dispose the enumerator. However, the while statement remains the same, compared with the previous example.


Even fancier, List.ForEach

This option only applies to collections of type List allowing to execute an action for each element of the list, see more.

        public void ForEachLambdaLoop()
        {
            sum = 0;
            elements.ForEach(
                x => sum += x
            );
        }

Looks nice to be honest, the method is expecting an Action that will be executed for each element in the collection. After lowering this code, this is how it looks like:

    public void ForEachLambdaLoop()
    {
        sum = 0;
        elements.ForEach(new Action<int>(<ForEachLambdaLoop>b__5_0));
    }

    [CompilerGenerated]
    private void <ForEachLambdaLoop>b__5_0(int x)
    {
        sum += x;
    }

Note that a new method to compute the arithmetic operation has been added and also decorated with CompilerGenerated.


How to compare all three options?

To get some metrics, I have created a sample project that you can download and use for your reference. In this case, I am using the BenchmarkDotNet package to analyze the code during its execution.

C:\temp\sharplab>dotnet add package BenchmarkDotNet

I have defined a basic class including three methods for each option discussed above. It is important to execute the code in Release mode for Benchmark to work.

C:\temp\sharplab>dotnet run -c release

The results

It is kind of shocking that a for statement executes in less than half of the time of the other two options. By looking at the code, I already knew that it would be the fastest option, but I had no idea how fast!

Summary

Two things to take away from this article:

By the way, if someone says that your code is not efficient because you are initializing a List and later, adding each element by calling the .Add() function. They are wrong, it is the same thing after lowering.

Visit: https://sharplab.io/

private readonly List<int> elements;
private int sum = 0;

public Loop()
{
    elements = new List<int>() {1,2,3,4,5,6,7,8,9,10};
}
private readonly List<int> elements;

private int sum = 0;

public Loop()
{
  List<int> list = new List<int>();
  list.Add(1);
  list.Add(2);
  list.Add(3);
  list.Add(4);
  list.Add(5);
  list.Add(6);
  list.Add(7);
  list.Add(8);
  list.Add(9);
  list.Add(10);
  elements = list;
}

Cheers!