There are several ways to combine two policies. Let me list all the different options for the sake of completeness. I will add recommendations for each: when to use it and when to avoid it
Branch on exception
ExceptionDispatchInfo edi = null;
int retryCounter = 0;
var combinedPolicy = Policy
       .Handle<DivideByZeroException>()
       .Or<StackOverflowException>()
       .WaitAndRetry(CalculateSleep());
combinedPolicy.Execute(() => {
    try
    {
        //User code...
        throw new DivideByZeroException();
    }
    catch (Exception ex)
    {
        edi = ExceptionDispatchInfo.Capture(ex);
        edi.Throw();
    }
});
IEnumerable<TimeSpan> CalculateSleep()
{
    while (true)
    {
        var wasItADivideByZero = edi.SourceException is DivideByZeroException;
        var attempt = retryCounter++;
        if (wasItADivideByZero)
        {
            if (attempt > 3) break;
            yield return TimeSpan.FromSeconds(1);
        }
        yield return TimeSpan.FromSeconds(0);
    }
}
Notes
- The 
WaitAndRetry does not have an overload where you don't have to specify the retryCount but you can access the thrown exception inside the sleepDurations 
- So, the built-in options support: either use 
retryCount + sleepDurationProvider (from where you can access the exception) or use sleepDurations 
- That's why we have to use 
ExceptionDispatchInfo to capture the thrown exception and access it inside our custom CalculateSleep method 
Use it
- If you would have two policies with the same amount of maximum retry attempts but with different sleep and different trigger, like this
 
var combinedPolicy = Policy
       .Handle<DivideByZeroException>()
       .Or<StackOverflowException>()
       .WaitAndRetry(3,
       (_, ex, __) => TimeSpan.FromSeconds(ex is DivideByZeroException ? 1 : 2),
       (_, __, ___, ____) => { });
Avoid it
- Do not re-invent the wheel if you have other alternatives
 
- Even though the above solution works like a charm it is complex and hard to maintain
 
Explicit continuation
policy1.Execute(() =>
{
    policy2.Execute(() =>
    {
        //User code
    });
});
OR
policy2.Execute(() =>
{
    policy1.Execute(() =>
    {
        //User code
    });
});
Notes
- The order does not matter here, because the two policies are independent
 
- So, if we would have a retry and a timeout policy then the order would matter
- Outer: Timeout; Inner: Retry >> Timeout is a global, overarching
 
- Outer: Retry; Inner: Timeout >> Timeout is local (so per attempt)
 
 
Use it
- If you have no more than two policies and the to-be-decorated code is extracted into a separate method, like this
 
policy1.Execute(policy2.Execute(UserCode()));
Avoid it
The Wrap instance method
var combinedPolicy = policy1.Wrap(policy2);
combinedPolicy.Execute(UserCode());
OR
var combinedPolicy = policy2.Wrap(policy1);
combinedPolicy.Execute(UserCode());
Notes
- We could separate the policy definition from its execution
 
- The ordering does not matter here as well
 
- The 
combinedPolicy's type is PolicyWrap, which implements the ISyncPolicy interface as well 
Use it
- When you want to separate the policy definition from its usage
 
- It is suitable for combining no more than two policies
 
Avoid it
- If you want to combine more than 3 then it looks ugly IMHO
 
var combinedPolicy = globalTimeout.Wrap(retry).Wrap(circuitBreaker).Wrap(localTimeout);
- If you have a policy for a 
void and another policy for TResult then the system tries to act smart
- The type of the 
combinedPolicy is now PolicyWrap<string> 
 
- This combination is may or may not be intentional
- If unintentional then it will not warn you about it
 
 
var policy1 = Policy.Handle<DivideByZeroException>().WaitAndRetry(3, i => TimeSpan.FromSeconds(1));
var policy2 = Policy<string>.Handle<StackOverflowException>().RetryForever();
var combinedPolicy = policy1.Wrap(policy2);
string? result = combinedPolicy.Execute(() => "dummy response");
The Wrap static method
var combinedPolicy = Policy.Wrap(policy2, policy1);
OR
var combinedPolicy = Policy.Wrap(policy2, policy1);
Notes
- This static method can receive any number of 
ISyncPolicy or ISyncPolicy<TResult> policies 
Use it
- With this method you could not combine a 
ISyncPolicy and a ISyncPolicy<string> policies because all policies should have the same type 
var combinedPolicy = Policy.Wrap<HttpResponseMessage>(globalTimeout,retry,circuitBreaker,localTimeout);
Avoid it
- When you want to explicitly combine two or more policies where you have a 
TResult and one or more void
- Or multiple 
TResult and one or more void 
 
- Please note: You can not combine two policies where you have 
TResult1 and TResult2